mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-11-15 17:55:08 +00:00
Compare commits
11
Commits
alex/fix-leak
...
2.2.45
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9493d630a0 | ||
|
|
aee989d23b | ||
|
|
3876218405 | ||
|
|
a6d014a9eb | ||
|
|
52444510d0 | ||
|
|
8840c2109f | ||
|
|
e64a4e7214 | ||
|
|
e46eb46343 | ||
|
|
5da38c9180 | ||
|
|
85bde5827f | ||
|
|
ea2d34589e |
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;
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ public final class SystemProperties {
|
||||
public static final String GL_PROFILE = System.getProperty("angelica.glProfile", "");
|
||||
public static final boolean DISABLE_NVIDIA_WORKAROUND = Boolean.getBoolean("angelica.disableNvidiaWorkaround");
|
||||
public static final boolean DISABLE_LTW_WORKAROUND = Boolean.getBoolean("angelica.disableLtwWorkaround");
|
||||
public static final boolean EMULATE_CLIP_DISTANCES = Boolean.getBoolean("angelica.emulateClipDistances");
|
||||
|
||||
// Redirector
|
||||
public static final UnmappedGLMode UNMAPPED_GL = parseEnum("angelica.unmappedGL", UnmappedGLMode.WARN, UnmappedGLMode.WARN, UnmappedGLMode.class);
|
||||
@@ -45,6 +46,8 @@ public final class SystemProperties {
|
||||
public static final Boolean TRACY_FINE_ZONES_OVERRIDE = parseBooleanOverride("angelica.tracy.fineZones");
|
||||
public static final String TRACY_DIR = System.getProperty("angelica.tracy.dir", "angelica" + File.separator + "natives" + File.separator + "tracy");
|
||||
public static final Integer TRACY_MAX_SRC_LOCS_OVERRIDE = Integer.getInteger("angelica.tracy.maxSrcLocs");
|
||||
public static final Boolean TRACY_FRAME_IMAGES_OVERRIDE = parseBooleanOverride("angelica.tracy.frameImages");
|
||||
public static final Integer TRACY_FRAME_IMAGE_RATE_OVERRIDE = Integer.getInteger("angelica.tracy.frameImageRate");
|
||||
|
||||
// Profiling
|
||||
public static final String PROFILE_OPTS = System.getProperty("angelica.profile.opts", "event=wall,interval=5ms,alloc=512k,lock=10ms");
|
||||
|
||||
@@ -25,11 +25,13 @@ public final class CaptureGate {
|
||||
public static final boolean FORCED = SystemProperties.DEBUG_MARKERS || SystemProperties.LWJGL_DEBUG;
|
||||
public static final boolean TOOL_ATTACHED = detectCaptureTool();
|
||||
public static boolean markersThisFrame = true;
|
||||
public static boolean tracyConnected;
|
||||
|
||||
private CaptureGate() {}
|
||||
|
||||
public static void refresh() {
|
||||
markersThisFrame = gateValue(FORCED, TOOL_ATTACHED, Tracy.isConnected());
|
||||
tracyConnected = Tracy.isConnected();
|
||||
markersThisFrame = gateValue(FORCED, TOOL_ATTACHED, tracyConnected);
|
||||
}
|
||||
|
||||
static boolean gateValue(boolean forced, boolean toolAttached, boolean tracyConnected) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
|
||||
|
||||
public final class ClipPlaneBooleanState extends BooleanStateStack {
|
||||
@@ -13,12 +14,22 @@ public final class ClipPlaneBooleanState extends BooleanStateStack {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean forwardsToBackend() {
|
||||
return !RenderSystem.isClipDistanceEmulated();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEnabledChanged() {
|
||||
final int before = owner.clipPlaneEnabledMask;
|
||||
if (isEnabled()) {
|
||||
owner.clipPlaneEnabledMask |= (1 << index);
|
||||
} else {
|
||||
owner.clipPlaneEnabledMask &= ~(1 << index);
|
||||
}
|
||||
owner.clipPlaneGeneration++;
|
||||
if ((before == 0) != (owner.clipPlaneEnabledMask == 0) && RenderSystem.isClipDistanceEmulated() && GLSMHooks.CLIP_PLANES_IN_USE.hasListeners()) {
|
||||
GLSMHooks.CLIP_PLANES_IN_USE.post(GLSMHooks.clipPlanesInUseEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -249,7 +249,7 @@ public class CompatUniformManager {
|
||||
final int cpGen = glCtx.clipPlaneGeneration;
|
||||
if (cpGen != pu.lastClipPlaneGen) {
|
||||
pu.lastClipPlaneGen = cpGen;
|
||||
uploadClipPlanes(locs);
|
||||
uploadClipPlanes(locs, glCtx.clipPlaneEnabledMask);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -468,16 +468,14 @@ public class CompatUniformManager {
|
||||
RENDER_BACKEND.uniform1f(locs[LOC_MAT_BASE + MF_SHININESS], mat.shininess);
|
||||
}
|
||||
|
||||
private static void uploadClipPlanes(int[] locs) {
|
||||
private static void uploadClipPlanes(int[] locs, int enabledMask) {
|
||||
if (locs[LOC_CLIP_PLANES_ENABLED] != -1) {
|
||||
RENDER_BACKEND.uniform1i(locs[LOC_CLIP_PLANES_ENABLED], GLSMRasterizer.anyClipPlaneEnabled() ? 1 : 0);
|
||||
RENDER_BACKEND.uniform1i(locs[LOC_CLIP_PLANES_ENABLED], enabledMask != 0 ? 1 : 0);
|
||||
}
|
||||
if (locs[LOC_CLIP_PLANES] != -1) {
|
||||
final ClipPlaneState cps = GLSMRasterizer.getClipPlaneState();
|
||||
clipPlaneBuf.clear();
|
||||
for (int i = 0; i < GLStateManager.MAX_CLIP_PLANES; i++) {
|
||||
cps.putEyePlane(i, clipPlaneBuf);
|
||||
}
|
||||
cps.putEffectivePlanes(enabledMask, clipPlaneBuf);
|
||||
clipPlaneBuf.flip();
|
||||
RENDER_BACKEND.uniform4(locs[LOC_CLIP_PLANES], clipPlaneBuf);
|
||||
}
|
||||
|
||||
@@ -11,11 +11,8 @@ public final class GLESCaps {
|
||||
|
||||
private GLESCaps() {}
|
||||
|
||||
public static boolean isCapAllowed(int cap, boolean isGLES, boolean hasClipCullDistance) {
|
||||
public static boolean isCapAllowed(int cap, boolean isGLES) {
|
||||
if (!isGLES) return true;
|
||||
if (cap >= GL11.GL_CLIP_PLANE0 && cap < GL11.GL_CLIP_PLANE0 + GLStateManager.MAX_CLIP_PLANES) {
|
||||
return hasClipCullDistance;
|
||||
}
|
||||
return switch (cap) {
|
||||
case GL11.GL_LINE_SMOOTH,
|
||||
GL11.GL_POLYGON_SMOOTH,
|
||||
|
||||
@@ -30,7 +30,6 @@ public final class GLSMEnable {
|
||||
// Handle clip planes dynamically (supports up to MAX_CLIP_PLANES)
|
||||
if (cap >= GL11.GL_CLIP_PLANE0 && cap < GL11.GL_CLIP_PLANE0 + MAX_CLIP_PLANES) {
|
||||
glCtx.clipPlaneStates[cap - GL11.GL_CLIP_PLANE0].enable();
|
||||
glCtx.clipPlaneGeneration++;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -119,7 +118,6 @@ public final class GLSMEnable {
|
||||
// Handle clip planes dynamically (supports up to MAX_CLIP_PLANES)
|
||||
if (cap >= GL11.GL_CLIP_PLANE0 && cap < GL11.GL_CLIP_PLANE0 + MAX_CLIP_PLANES) {
|
||||
glCtx.clipPlaneStates[cap - GL11.GL_CLIP_PLANE0].disable();
|
||||
glCtx.clipPlaneGeneration++;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.DisplayListManager.RecordMode;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.ScreenCapture;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.IntegerStateStack;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
@@ -326,6 +327,11 @@ public final class GLSMFramebuffer {
|
||||
RENDER_BACKEND.readPixels(x, y, width, height, format, GLSMTextures.remapPixelTypeForGLES(format, type), pixels);
|
||||
}
|
||||
|
||||
public static ScreenCapture createScreenCapture(int width, int height) {
|
||||
rejectExitCapture();
|
||||
return RENDER_BACKEND.createScreenCapture(width, height);
|
||||
}
|
||||
|
||||
public static int getDrawFramebufferGeneration() {
|
||||
return ctx().drawFramebufferGeneration;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
@@ -506,6 +509,14 @@ public final class GLSMRasterizer {
|
||||
return glCtx.clipPlaneEnabledMask != 0;
|
||||
}
|
||||
|
||||
public static boolean clipEmulationActive() {
|
||||
return RenderSystem.isClipDistanceEmulated() && anyClipPlaneEnabled();
|
||||
}
|
||||
|
||||
public static boolean clipEmulationActive(GLContextState glCtx) {
|
||||
return RenderSystem.isClipDistanceEmulated() && anyClipPlaneEnabled(glCtx);
|
||||
}
|
||||
|
||||
public static void glSampleCoverage(float value, boolean invert) {
|
||||
if (DisplayListManager.isRecording()) {
|
||||
throw DisplayListManager.unsupportedInList("glSampleCoverage");
|
||||
|
||||
@@ -1211,6 +1211,14 @@ public final class GLSMTextures {
|
||||
public static void restorePixelUnpackState(PixelStoreState applied) {
|
||||
PixelStoreState.applyDiff(applied, ctx().pixelUnpackState, false);
|
||||
}
|
||||
|
||||
public static void forcePixelPackState(PixelStoreState target) {
|
||||
PixelStoreState.applyDiff(ctx().pixelPackState, target, true);
|
||||
}
|
||||
|
||||
public static void restorePixelPackState(PixelStoreState applied) {
|
||||
PixelStoreState.applyDiff(applied, ctx().pixelPackState, true);
|
||||
}
|
||||
public static boolean glIsTexture(int texture) { rejectExitCapture(); return !GLSMTextureLifetime.isDeferredDelete(texture) && RENDER_BACKEND.isTexture(texture); }
|
||||
public static boolean glIsSampler(int sampler) { rejectExitCapture(); return RENDER_BACKEND.isSampler(sampler); }
|
||||
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.backend.ScreenCapture;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.BatchStateGuard;
|
||||
import com.gtnewhorizons.angelica.glsm.states.PixelStoreState;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL21;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public final class GLScreenCapture extends ScreenCapture {
|
||||
|
||||
private final int[] textures = new int[RING];
|
||||
private final int[] framebuffers = new int[RING];
|
||||
private final int[] buffers = new int[RING];
|
||||
|
||||
public GLScreenCapture(int width, int height) {
|
||||
super(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean start(int slot, int srcFramebuffer, int srcWidth, int srcHeight) {
|
||||
final int width = width();
|
||||
final int height = height();
|
||||
final int previousRead = GLSMFramebuffer.getReadFramebuffer();
|
||||
final int previousDraw = GLSMFramebuffer.getDrawFramebuffer();
|
||||
final int previousPack = GLStateManager.ctx().boundPixelPackBuffer;
|
||||
final boolean scissor = GLSMRasterizer.getScissorTest().isEnabled();
|
||||
BatchStateGuard.suspend();
|
||||
try {
|
||||
if (framebuffers[slot] == 0) create(slot);
|
||||
if (scissor) GLSMEnable.glDisable(GL11.GL_SCISSOR_TEST);
|
||||
GLSMFramebuffer.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, srcFramebuffer);
|
||||
GLSMFramebuffer.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, framebuffers[slot]);
|
||||
GLSMFramebuffer.glBlitFramebuffer(0, 0, srcWidth, srcHeight, 0, 0, width, height, GL11.GL_COLOR_BUFFER_BIT, GL11.GL_LINEAR);
|
||||
GLSMFramebuffer.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, framebuffers[slot]);
|
||||
GLSMBuffers.glBindBuffer(GL21.GL_PIXEL_PACK_BUFFER, buffers[slot]);
|
||||
GLSMTextures.forcePixelPackState(PixelStoreState.DEFAULT);
|
||||
GLSMFramebuffer.glReadPixels(0, 0, width, height, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, 0L);
|
||||
return true;
|
||||
} finally {
|
||||
GLSMTextures.restorePixelPackState(PixelStoreState.DEFAULT);
|
||||
GLSMBuffers.glBindBuffer(GL21.GL_PIXEL_PACK_BUFFER, previousPack);
|
||||
GLSMFramebuffer.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, previousRead);
|
||||
GLSMFramebuffer.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, previousDraw);
|
||||
if (scissor) GLSMEnable.glEnable(GL11.GL_SCISSOR_TEST);
|
||||
BatchStateGuard.resume();
|
||||
}
|
||||
}
|
||||
|
||||
private void create(int slot) {
|
||||
final int previousTexture = GLSMTextures.getBoundTextureForServerState();
|
||||
final int texture;
|
||||
try {
|
||||
texture = GLSMTextures.glGenTextures();
|
||||
GLSMTextures.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
GLSMTextures.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
|
||||
GLSMTextures.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
|
||||
GLSMTextures.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE);
|
||||
GLSMTextures.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE);
|
||||
GLSMTextures.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, 0);
|
||||
GLSMTextures.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, width(), height(), 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
} finally {
|
||||
GLSMTextures.glBindTexture(GL11.GL_TEXTURE_2D, previousTexture);
|
||||
}
|
||||
textures[slot] = texture;
|
||||
framebuffers[slot] = GLSMFramebuffer.glGenFramebuffers();
|
||||
GLSMFramebuffer.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, framebuffers[slot]);
|
||||
GLSMFramebuffer.glFramebufferTexture2D(GL30.GL_DRAW_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, texture, 0);
|
||||
buffers[slot] = GLSMBuffers.glGenBuffers();
|
||||
GLSMBuffers.glBindBuffer(GL21.GL_PIXEL_PACK_BUFFER, buffers[slot]);
|
||||
GLSMBuffers.glBufferData(GL21.GL_PIXEL_PACK_BUFFER, (long) width() * height() * 4, GL15.GL_STREAM_READ);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long map(int slot) {
|
||||
final int previousPack = GLStateManager.ctx().boundPixelPackBuffer;
|
||||
GLSMBuffers.glBindBuffer(GL21.GL_PIXEL_PACK_BUFFER, buffers[slot]);
|
||||
try {
|
||||
return GLSMBuffers.glMapBufferRangeAddress(GL21.GL_PIXEL_PACK_BUFFER, 0L, (long) width() * height() * 4, GL30.GL_MAP_READ_BIT);
|
||||
} finally {
|
||||
GLSMBuffers.glBindBuffer(GL21.GL_PIXEL_PACK_BUFFER, previousPack);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void unmap(int slot) {
|
||||
final int previousPack = GLStateManager.ctx().boundPixelPackBuffer;
|
||||
GLSMBuffers.glBindBuffer(GL21.GL_PIXEL_PACK_BUFFER, buffers[slot]);
|
||||
GLSMBuffers.glUnmapBuffer(GL21.GL_PIXEL_PACK_BUFFER);
|
||||
GLSMBuffers.glBindBuffer(GL21.GL_PIXEL_PACK_BUFFER, previousPack);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deleteSlot(int slot) {
|
||||
if (buffers[slot] != 0) GLSMBuffers.glDeleteBuffers(buffers[slot]);
|
||||
if (framebuffers[slot] != 0) GLSMFramebuffer.glDeleteFramebuffers(framebuffers[slot]);
|
||||
if (textures[slot] != 0) GLSMTextures.glDeleteTextures(textures[slot]);
|
||||
buffers[slot] = 0;
|
||||
framebuffers[slot] = 0;
|
||||
textures[slot] = 0;
|
||||
}
|
||||
}
|
||||
@@ -63,7 +63,7 @@ public class RenderSystem {
|
||||
private static volatile boolean isGLES;
|
||||
private static volatile int glesVersion;
|
||||
private static volatile boolean glesDetected;
|
||||
private static volatile boolean hasClipCullDistance;
|
||||
private static boolean clipDistanceEmulated;
|
||||
|
||||
private static volatile boolean isLTW;
|
||||
private static volatile boolean ltwDetected;
|
||||
@@ -132,7 +132,12 @@ public class RenderSystem {
|
||||
supportsClearTexture = GLStateManager.capabilities.OpenGL44 || GLStateManager.capabilities.GL_ARB_clear_texture;
|
||||
supportsStencilTexturing = GLStateManager.capabilities.OpenGL43 || GLStateManager.capabilities.GL_ARB_stencil_texturing;
|
||||
supportsTextureStorage = isGLES ? glesVersion >= 300 : GLStateManager.capabilities.OpenGL42 || GLStateManager.capabilities.GL_ARB_texture_storage;
|
||||
hasClipCullDistance = !isGLES || hasExtension("GL_EXT_clip_cull_distance");
|
||||
final boolean esClipExt = !isGLES || hasExtension("GL_EXT_clip_cull_distance");
|
||||
final int maxClipDistances = esClipExt ? RENDER_BACKEND.getInteger(GL30.GL_MAX_CLIP_DISTANCES) : 0;
|
||||
clipDistanceEmulated = SystemProperties.EMULATE_CLIP_DISTANCES || maxClipDistances < GLStateManager.MAX_CLIP_PLANES;
|
||||
if (clipDistanceEmulated) {
|
||||
GLStateManager.LOGGER.info("Clip distances emulated (GL_MAX_CLIP_DISTANCES={}, forced={})", maxClipDistances, SystemProperties.EMULATE_CLIP_DISTANCES);
|
||||
}
|
||||
|
||||
// Cache maximum image units
|
||||
if (supportsImageLoadStore) {
|
||||
@@ -434,8 +439,8 @@ public class RenderSystem {
|
||||
return supportsImageLoadStore;
|
||||
}
|
||||
|
||||
public static boolean hasClipCullDistance() {
|
||||
return hasClipCullDistance;
|
||||
public static boolean isClipDistanceEmulated() {
|
||||
return clipDistanceEmulated;
|
||||
}
|
||||
|
||||
private static boolean hasExtension(String name) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.glsm.backend;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLScreenCapture;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import org.lwjgl.BufferUtils;
|
||||
@@ -659,6 +660,11 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
|
||||
GL11.glReadBuffer(mode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScreenCapture createScreenCapture(int width, int height) {
|
||||
return new GLScreenCapture(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readPixels(int x, int y, int width, int height, int format, int type, ByteBuffer pixels) {
|
||||
GL11.glReadPixels(x, y, width, height, format, type, pixels);
|
||||
|
||||
@@ -479,6 +479,7 @@ public abstract class RenderBackend {
|
||||
public abstract void readPixels(int x, int y, int width, int height, int format, int type, FloatBuffer pixels);
|
||||
public abstract void readPixels(int x, int y, int width, int height, int format, int type, IntBuffer pixels);
|
||||
public abstract void readPixels(int x, int y, int width, int height, int format, int type, long pixelBufferOffset);
|
||||
public abstract ScreenCapture createScreenCapture(int width, int height);
|
||||
public abstract void getTexImage(int target, int level, int format, int type, ByteBuffer pixels);
|
||||
public abstract void getTexImage(int target, int level, int format, int type, IntBuffer pixels);
|
||||
public abstract void getTexImage(int target, int level, int format, int type, long pixelBufferOffset);
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
package com.gtnewhorizons.angelica.glsm.backend;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMBuffers;
|
||||
import org.lwjgl.opengl.GL32;
|
||||
|
||||
/** Async downscaled RGBA8 copy of a framebuffer's color. */
|
||||
public abstract class ScreenCapture {
|
||||
|
||||
public static final int RING = RenderBackend.MAX_FRAMES_IN_FLIGHT + 2;
|
||||
|
||||
private final int width;
|
||||
private final int height;
|
||||
private final long[] tags = new long[RING];
|
||||
private final long[] syncs = new long[RING];
|
||||
private int head;
|
||||
private int tail;
|
||||
private long mapped;
|
||||
private boolean deleted;
|
||||
|
||||
protected ScreenCapture(int width, int height) {
|
||||
if (width <= 0 || height <= 0) throw new IllegalArgumentException("capture size " + width + "x" + height);
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public final int width() { return width; }
|
||||
public final int height() { return height; }
|
||||
|
||||
public final boolean idle() { return head == tail; }
|
||||
|
||||
public final boolean begin(int srcFramebuffer, int srcWidth, int srcHeight, long tag) {
|
||||
if (deleted || head - tail == RING || srcWidth <= 0 || srcHeight <= 0) return false;
|
||||
final int slot = head % RING;
|
||||
if (!start(slot, srcFramebuffer, srcWidth, srcHeight)) return false;
|
||||
final long sync = GLSMBuffers.glFenceSync(GL32.GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
if (sync == 0L) return false;
|
||||
syncs[slot] = sync;
|
||||
tags[slot] = tag;
|
||||
head++;
|
||||
return true;
|
||||
}
|
||||
|
||||
public final boolean poll() {
|
||||
if (mapped != 0L) throw new IllegalStateException("poll() before release() of the previous capture");
|
||||
while (tail != head) {
|
||||
final int slot = tail % RING;
|
||||
final int status = GLSMBuffers.glClientWaitSync(syncs[slot], 0, 0L);
|
||||
if (status == GL32.GL_TIMEOUT_EXPIRED) return false;
|
||||
dropSync(slot);
|
||||
if (status != GL32.GL_WAIT_FAILED) {
|
||||
mapped = map(slot);
|
||||
if (mapped != 0L) return true;
|
||||
}
|
||||
tail++;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public final long address() {
|
||||
if (mapped == 0L) throw new IllegalStateException("no polled capture");
|
||||
return mapped;
|
||||
}
|
||||
|
||||
public final long tag() {
|
||||
if (mapped == 0L) throw new IllegalStateException("no polled capture");
|
||||
return tags[tail % RING];
|
||||
}
|
||||
|
||||
public final void release() {
|
||||
if (mapped == 0L) throw new IllegalStateException("no polled capture");
|
||||
mapped = 0L;
|
||||
unmap(tail % RING);
|
||||
tail++;
|
||||
}
|
||||
|
||||
public final void delete() {
|
||||
if (deleted) return;
|
||||
if (mapped != 0L) release();
|
||||
deleted = true;
|
||||
for (int slot = 0; slot < RING; slot++) {
|
||||
dropSync(slot);
|
||||
deleteSlot(slot);
|
||||
}
|
||||
head = tail = 0;
|
||||
}
|
||||
|
||||
private void dropSync(int slot) {
|
||||
if (syncs[slot] == 0L) return;
|
||||
GLSMBuffers.glDeleteSync(syncs[slot]);
|
||||
syncs[slot] = 0L;
|
||||
}
|
||||
|
||||
/** Returns false when nothing was recorded; the slot then stays free. */
|
||||
protected abstract boolean start(int slot, int srcFramebuffer, int srcWidth, int srcHeight);
|
||||
|
||||
protected abstract long map(int slot);
|
||||
|
||||
protected abstract void unmap(int slot);
|
||||
|
||||
/** Called for every slot, including ones never started. */
|
||||
protected abstract void deleteSlot(int slot);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
public final class ClipDistanceGlsl {
|
||||
|
||||
public static final String VS_OUT_DIST0 = "out vec4 angelica_ClipDist0;";
|
||||
public static final String VS_OUT_DIST1 = "out vec4 angelica_ClipDist1;";
|
||||
public static final String FS_IN_DIST0 = "in vec4 angelica_ClipDist0;";
|
||||
public static final String FS_IN_DIST1 = "in vec4 angelica_ClipDist1;";
|
||||
public static final String FS_DISCARD = "if (any(lessThan(angelica_ClipDist0, vec4(0.0))) || any(lessThan(angelica_ClipDist1, vec4(0.0)))) discard;\n";
|
||||
|
||||
private ClipDistanceGlsl() {}
|
||||
|
||||
public static void appendVsAssign(StringBuilder sb, String[] distances) {
|
||||
sb.append(" angelica_ClipDist0 = vec4(").append(distances[0]).append(", ").append(distances[1]).append(", ").append(distances[2]).append(", ").append(distances[3]).append(");\n");
|
||||
sb.append(" angelica_ClipDist1 = vec4(").append(distances[4]).append(", ").append(distances[5]).append(", ").append(distances[6]).append(", ").append(distances[7]).append(");\n");
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMCurrentAttribs;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMLighting;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMMatrices;
|
||||
import com.gtnewhorizons.angelica.glsm.states.LightState;
|
||||
@@ -15,12 +16,75 @@ public final class FFPVertexLighting {
|
||||
private static final Vector4f scratchEyePos = new Vector4f();
|
||||
private static final Matrix3f scratchNormalMat = new Matrix3f();
|
||||
private static final Vector3f scratchVec = new Vector3f();
|
||||
private static final Vector3f scratchFactor = new Vector3f();
|
||||
private static final Vector3f base = new Vector3f();
|
||||
private static final Vector3f[] lightDirs = new Vector3f[VertexKey.FFP_LIGHT_COUNT];
|
||||
private static final Vector3f[] lightDiffuse = new Vector3f[VertexKey.FFP_LIGHT_COUNT];
|
||||
private static int litCount;
|
||||
private static boolean normalize;
|
||||
private static float rescale;
|
||||
|
||||
static {
|
||||
for (int i = 0; i < VertexKey.FFP_LIGHT_COUNT; i++) {
|
||||
lightDirs[i] = new Vector3f();
|
||||
lightDiffuse[i] = new Vector3f();
|
||||
}
|
||||
}
|
||||
|
||||
private FFPVertexLighting() {}
|
||||
|
||||
public static boolean modulatesVertexColor(Vector3f destFactor) {
|
||||
destFactor.set(1.0f);
|
||||
if (!prepare(false)) return false;
|
||||
|
||||
factor(ShaderManager.getCurrentNormal(), scratchFactor);
|
||||
final float r = scratchFactor.x;
|
||||
final float g = scratchFactor.y;
|
||||
final float b = scratchFactor.z;
|
||||
if (r == 1.0f && g == 1.0f && b == 1.0f) return false;
|
||||
if (!Float.isFinite(r + g + b)) return false;
|
||||
destFactor.set(r, g, b);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean bakeColors(int[] rawBuffer, int vertexCount, boolean hasNormals, boolean hasColor) {
|
||||
if (vertexCount <= 0 || !prepare(true)) return false;
|
||||
|
||||
if (!hasNormals) factor(ShaderManager.getCurrentNormal(), scratchFactor);
|
||||
float cr = 0.0f, cg = 0.0f, cb = 0.0f;
|
||||
int alphaBits = 0;
|
||||
if (!hasColor) {
|
||||
final var color = GLSMCurrentAttribs.getColor();
|
||||
cr = clamp01(color.getRed());
|
||||
cg = clamp01(color.getGreen());
|
||||
cb = clamp01(color.getBlue());
|
||||
alphaBits = toByte(clamp01(color.getAlpha())) << 24;
|
||||
}
|
||||
|
||||
boolean haveNormal = false;
|
||||
int lastNormal = 0;
|
||||
for (int v = 0, i = 0; v < vertexCount; v++, i += 8) {
|
||||
if (hasNormals) {
|
||||
final int packed = rawBuffer[i + 6];
|
||||
if (!haveNormal || packed != lastNormal) {
|
||||
factor((byte) packed / 127.0f, (byte) (packed >> 8) / 127.0f, (byte) (packed >> 16) / 127.0f, scratchFactor);
|
||||
lastNormal = packed;
|
||||
haveNormal = true;
|
||||
}
|
||||
}
|
||||
if (hasColor) {
|
||||
final int packed = rawBuffer[i + 5];
|
||||
cr = (packed & 0xFF) / 255.0f;
|
||||
cg = ((packed >>> 8) & 0xFF) / 255.0f;
|
||||
cb = ((packed >>> 16) & 0xFF) / 255.0f;
|
||||
alphaBits = packed & 0xFF000000;
|
||||
}
|
||||
rawBuffer[i + 5] = alphaBits | (toByte(clamp01(cb * scratchFactor.z)) << 16) | (toByte(clamp01(cg * scratchFactor.y)) << 8) | toByte(clamp01(cr * scratchFactor.x));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean prepare(boolean directionalOnly) {
|
||||
if (!GLSMLighting.getLightingState().isEnabled()) return false;
|
||||
if (!GLSMLighting.getColorMaterial().isEnabled()) return false;
|
||||
if (VertexKey.encodeColorMaterialMode(GLSMLighting.getColorMaterialParameter().getValue()) != VertexKey.CM_AMBIENT_AND_DIFFUSE) return false;
|
||||
@@ -32,44 +96,58 @@ public final class FFPVertexLighting {
|
||||
final boolean materialHasSpecular = material.shininess > 0.0f && (material.specular.x != 0.0f || material.specular.y != 0.0f || material.specular.z != 0.0f);
|
||||
|
||||
final Vector4f lightModelAmbient = GLSMLighting.getLightModel().ambient;
|
||||
float r = lightModelAmbient.x;
|
||||
float g = lightModelAmbient.y;
|
||||
float b = lightModelAmbient.z;
|
||||
base.set(lightModelAmbient.x, lightModelAmbient.y, lightModelAmbient.z);
|
||||
|
||||
final var lightEnables = GLSMLighting.getLightStates();
|
||||
final var lightData = GLSMLighting.getLightDataStates();
|
||||
boolean normalReady = false;
|
||||
litCount = 0;
|
||||
|
||||
for (int i = 0; i < VertexKey.FFP_LIGHT_COUNT; i++) {
|
||||
if (!lightEnables[i].isEnabled()) continue;
|
||||
final LightState light = lightData[i];
|
||||
if (materialHasSpecular && (light.specular.x != 0.0f || light.specular.y != 0.0f || light.specular.z != 0.0f))
|
||||
return false;
|
||||
if (!normalReady) {
|
||||
transformCurrentNormal();
|
||||
normalReady = true;
|
||||
}
|
||||
final float nDotVP = Math.max(scratchNormal.dot(lightDirection(light)), 0.0f);
|
||||
r += light.ambient.x + nDotVP * light.diffuse.x;
|
||||
g += light.ambient.y + nDotVP * light.diffuse.y;
|
||||
b += light.ambient.z + nDotVP * light.diffuse.z;
|
||||
if (directionalOnly && light.position.w != 0.0f) return false;
|
||||
base.add(light.ambient.x, light.ambient.y, light.ambient.z);
|
||||
lightDirs[litCount].set(lightDirection(light));
|
||||
lightDiffuse[litCount].set(light.diffuse.x, light.diffuse.y, light.diffuse.z);
|
||||
litCount++;
|
||||
}
|
||||
|
||||
if (r == 1.0f && g == 1.0f && b == 1.0f) return false;
|
||||
if (!Float.isFinite(r + g + b)) return false;
|
||||
destFactor.set(r, g, b);
|
||||
if (litCount > 0) {
|
||||
GLSMMatrices.getModelViewMatrix().normal(scratchNormalMat);
|
||||
normalize = GLSMLighting.getNormalizeState().isEnabled();
|
||||
rescale = !normalize && GLSMLighting.getRescaleNormalState().isEnabled() ? Uniforms.rescaleFactor(scratchNormalMat, scratchVec) : 1.0f;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void transformCurrentNormal() {
|
||||
GLSMMatrices.getModelViewMatrix().normal(scratchNormalMat);
|
||||
scratchNormal.set(ShaderManager.getCurrentNormal());
|
||||
private static void factor(Vector3f normal, Vector3f dest) {
|
||||
factor(normal.x, normal.y, normal.z, dest);
|
||||
}
|
||||
|
||||
private static void factor(float nx, float ny, float nz, Vector3f dest) {
|
||||
dest.set(base);
|
||||
if (litCount == 0) return;
|
||||
scratchNormal.set(nx, ny, nz);
|
||||
scratchNormalMat.transform(scratchNormal);
|
||||
if (GLSMLighting.getNormalizeState().isEnabled()) {
|
||||
if (normalize) {
|
||||
scratchNormal.normalize();
|
||||
} else if (GLSMLighting.getRescaleNormalState().isEnabled()) {
|
||||
scratchNormal.mul(Uniforms.rescaleFactor(scratchNormalMat, scratchVec));
|
||||
} else {
|
||||
scratchNormal.mul(rescale);
|
||||
}
|
||||
for (int i = 0; i < litCount; i++) {
|
||||
final float nDotVP = Math.max(scratchNormal.dot(lightDirs[i]), 0.0f);
|
||||
dest.fma(nDotVP, lightDiffuse[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private static float clamp01(float value) {
|
||||
return Math.min(Math.max(value, 0.0f), 1.0f);
|
||||
}
|
||||
|
||||
private static int toByte(float unit) {
|
||||
return (int) (unit * 255.0f + 0.5f);
|
||||
}
|
||||
|
||||
private static Vector3f lightDirection(LightState light) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLContextState;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMRasterizer;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.TexEnvState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
|
||||
@@ -79,6 +80,7 @@ public final class FragmentKey {
|
||||
private static final int GLOBAL_BITS = 14;
|
||||
private static final int BIT_COMBINED_GLINT = 61;
|
||||
private static final int BIT_GLINT_REPLACE_ALPHA = 62;
|
||||
private static final int BIT_CLIP_DISCARD = 63;
|
||||
private static final int BIT_FOG_MODE = 0; // 2 bits
|
||||
private static final int BIT_ALPHA_TEST = 2; // 1 bit
|
||||
private static final int BIT_ALPHA_FUNC = 3; // 3 bits
|
||||
@@ -126,6 +128,7 @@ public final class FragmentKey {
|
||||
long global = 0;
|
||||
if (CombinedGlint.isActive()) global |= 1L << BIT_COMBINED_GLINT;
|
||||
if (CombinedGlint.replacesAlpha()) global |= 1L << BIT_GLINT_REPLACE_ALPHA;
|
||||
if (GLSMRasterizer.clipEmulationActive(glCtx)) global |= 1L << BIT_CLIP_DISCARD;
|
||||
|
||||
// Fog
|
||||
global |= activeFogMode(glCtx);
|
||||
@@ -259,6 +262,7 @@ public final class FragmentKey {
|
||||
|
||||
public int fogMode() { return (int) (packed[0] & 0x3); }
|
||||
public boolean combinedGlint() { return (packed[0] & (1L << BIT_COMBINED_GLINT)) != 0; }
|
||||
public boolean clipDiscard() { return (packed[0] & (1L << BIT_CLIP_DISCARD)) != 0; }
|
||||
public boolean glintReplaceAlpha() { return (packed[0] & (1L << BIT_GLINT_REPLACE_ALPHA)) != 0; }
|
||||
public int textureSupersamplingSamples() { return 1 << (int) ((packed[0] >> BIT_TEXTURE_SUPERSAMPLING) & 0x7); }
|
||||
public boolean alphaTestEnabled() { return ((packed[0] >> BIT_ALPHA_TEST) & 1) != 0; }
|
||||
|
||||
@@ -34,6 +34,9 @@ public final class FragmentShaderGenerator {
|
||||
}
|
||||
|
||||
sb.append("void main() {\n");
|
||||
if (key.clipDiscard()) {
|
||||
sb.append(ClipDistanceGlsl.FS_DISCARD);
|
||||
}
|
||||
emitLineStipple(sb, key);
|
||||
emitTextureSampling(sb, key);
|
||||
emitTexEnvChain(sb, key);
|
||||
@@ -49,6 +52,9 @@ public final class FragmentShaderGenerator {
|
||||
private static void emitInputs(StringBuilder sb, FragmentKey key) {
|
||||
sb.append("// Inputs from vertex shader\n");
|
||||
sb.append("in vec4 v_Color;\n");
|
||||
if (key.clipDiscard()) {
|
||||
sb.append(ClipDistanceGlsl.FS_IN_DIST0).append('\n').append(ClipDistanceGlsl.FS_IN_DIST1).append('\n');
|
||||
}
|
||||
if (key.overlayInstanced()) {
|
||||
sb.append("in vec4 v_Overlay;\n");
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLContextState;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMBuffers;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.FogState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.LightState;
|
||||
@@ -330,9 +329,7 @@ public class Uniforms {
|
||||
private void stageClipPlanes(GLContextState glCtx) {
|
||||
final ClipPlaneState cps = glCtx.clipPlaneState;
|
||||
clipPlaneBuf.clear();
|
||||
for (int i = 0; i < GLStateManager.MAX_CLIP_PLANES; i++) {
|
||||
cps.putEyePlane(i, clipPlaneBuf);
|
||||
}
|
||||
cps.putEffectivePlanes(glCtx.clipPlaneEnabledMask, clipPlaneBuf);
|
||||
final long src = memAddress0(clipPlaneBuf);
|
||||
for (int i = 0; i < 32 * 4; i += 4) {
|
||||
putInt(FFPUniformBlock.CLIP_PLANES + i, memGetInt(src + i));
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.gtnewhorizons.angelica.glsm.GLContextState;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMCurrentAttribs;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMRasterizer;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import com.gtnewhorizons.angelica.glsm.states.TexGenState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
|
||||
import org.joml.Matrix4f;
|
||||
@@ -53,6 +54,7 @@ public final class VertexKey {
|
||||
private static final int BIT_UNIT23_UV_FROM_UNIT0 = BIT_UNIT_TEXMAT_BASE + MAX_UNITS;
|
||||
private static final int BIT_LINE_STIPPLE = BIT_UNIT23_UV_FROM_UNIT0 + 1;
|
||||
private static final int BIT_INSTANCING = BIT_LINE_STIPPLE + 1;
|
||||
private static final int BIT_CLIP_EMULATED = BIT_INSTANCING + 3;
|
||||
private static final long INSTANCING_MASK = 0x7L;
|
||||
|
||||
static {
|
||||
@@ -105,6 +107,7 @@ public final class VertexKey {
|
||||
public int texGenModeQ() { return (int)((packed >> BIT_TEXGEN_Q) & 0x7); }
|
||||
public boolean texGenEnabled() { return texGenModeS() != TG_NONE || texGenModeT() != TG_NONE || texGenModeR() != TG_NONE || texGenModeQ() != TG_NONE; }
|
||||
public boolean clipPlanesEnabled() { return bit(BIT_CLIP_PLANES); }
|
||||
public boolean clipEmulated() { return bit(BIT_CLIP_EMULATED); }
|
||||
public boolean lineStipple() { return bit(BIT_LINE_STIPPLE); }
|
||||
public boolean lineQuads() { return bit(BIT_LINE_QUADS); }
|
||||
public Instancing instancing() { return Instancing.VALUES[(int) ((packed >> BIT_INSTANCING) & INSTANCING_MASK)]; }
|
||||
@@ -238,6 +241,7 @@ public final class VertexKey {
|
||||
|
||||
if (GLSMRasterizer.anyClipPlaneEnabled(glCtx)) {
|
||||
bits |= (1L << BIT_CLIP_PLANES);
|
||||
if (RenderSystem.isClipDistanceEmulated()) bits |= 1L << BIT_CLIP_EMULATED;
|
||||
}
|
||||
|
||||
if (glCtx.lineStippleActive) {
|
||||
@@ -279,6 +283,6 @@ public final class VertexKey {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("FFPVertexKey[0x%012X: lit=%b l0=%b l1=%b cm=%b fog=%b tex=%d%d%d%d texmat=%d%d%d%d col=%b nrm=%b vtex=%b vlm=%b tg=%d/%d/%d/%d clip=%b lquads=%b inst=%s]", packed,lightingEnabled(), lightEnabled(0), lightEnabled(1), colorMaterialEnabled(), fogEnabled(), unitTexCoordEnabled(0)?1:0, unitTexCoordEnabled(1)?1:0, unitTexCoordEnabled(2)?1:0, unitTexCoordEnabled(3)?1:0, unitTexMatEnabled(0)?1:0, unitTexMatEnabled(1)?1:0, unitTexMatEnabled(2)?1:0, unitTexMatEnabled(3)?1:0, hasVertexColor(), hasVertexNormal(), hasVertexTexCoord(), hasVertexLightmap(), texGenModeS(), texGenModeT(), texGenModeR(), texGenModeQ(), clipPlanesEnabled(), lineQuads(), instancing());
|
||||
return String.format("FFPVertexKey[0x%012X: lit=%b l0=%b l1=%b cm=%b fog=%b tex=%d%d%d%d texmat=%d%d%d%d col=%b nrm=%b vtex=%b vlm=%b tg=%d/%d/%d/%d clip=%b clipEmu=%b lquads=%b inst=%s]", packed,lightingEnabled(), lightEnabled(0), lightEnabled(1), colorMaterialEnabled(), fogEnabled(), unitTexCoordEnabled(0)?1:0, unitTexCoordEnabled(1)?1:0, unitTexCoordEnabled(2)?1:0, unitTexCoordEnabled(3)?1:0, unitTexMatEnabled(0)?1:0, unitTexMatEnabled(1)?1:0, unitTexMatEnabled(2)?1:0, unitTexMatEnabled(3)?1:0, hasVertexColor(), hasVertexNormal(), hasVertexTexCoord(), hasVertexLightmap(), texGenModeS(), texGenModeT(), texGenModeR(), texGenModeQ(), clipPlanesEnabled(), clipEmulated(), lineQuads(), instancing());
|
||||
}
|
||||
}
|
||||
@@ -111,6 +111,9 @@ public final class VertexShaderGenerator {
|
||||
if (key.lineStipple()) {
|
||||
sb.append("flat out vec2 v_LineStart;\n");
|
||||
}
|
||||
if (key.clipEmulated()) {
|
||||
sb.append(ClipDistanceGlsl.VS_OUT_DIST0).append('\n').append(ClipDistanceGlsl.VS_OUT_DIST1).append('\n');
|
||||
}
|
||||
if (key.separateSpecular()) {
|
||||
sb.append("out vec3 v_SpecularColor;\n");
|
||||
}
|
||||
@@ -398,10 +401,19 @@ public final class VertexShaderGenerator {
|
||||
|
||||
private static void emitClipDistances(StringBuilder sb, VertexKey key) {
|
||||
sb.append(" // Clip distances\n");
|
||||
final boolean emulated = key.clipEmulated();
|
||||
final String[] distances = emulated ? new String[8] : null;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
final String own = "dot(u_ClipPlane[" + i + "], eyePos)";
|
||||
final String distance = key.lineQuads() ? acrossLine(own, "dot(u_ClipPlane[" + i + "], lineEyeOther)") : own;
|
||||
sb.append(" gl_ClipDistance[").append(i).append("] = ").append(distance).append(";\n");
|
||||
if (emulated) {
|
||||
distances[i] = distance;
|
||||
} else {
|
||||
sb.append(" gl_ClipDistance[").append(i).append("] = ").append(distance).append(";\n");
|
||||
}
|
||||
}
|
||||
if (emulated) {
|
||||
ClipDistanceGlsl.appendVsAssign(sb, distances);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ public final class GLSMHooks {
|
||||
public static final EventBus<ProgramDeleteEvent> PROGRAM_DELETE = EventBus.create(ProgramDeleteEvent.class);
|
||||
public static final EventBus<BlendFuncChangeEvent> BLEND_FUNC_CHANGE = EventBus.create(BlendFuncChangeEvent.class);
|
||||
public static final EventBus<FogStateChangeEvent> FOG_STATE_CHANGE = EventBus.create(FogStateChangeEvent.class);
|
||||
public static final EventBus<ClipPlanesInUseEvent> CLIP_PLANES_IN_USE = EventBus.create(ClipPlanesInUseEvent.class);
|
||||
public static final EventBus<LightmapCoordsEvent> LIGHTMAP_COORDS = EventBus.create(LightmapCoordsEvent.class);
|
||||
public static final EventBus<AlphaStateChangeEvent> ALPHA_STATE_CHANGE = EventBus.create(AlphaStateChangeEvent.class);
|
||||
public static final EventBus<ShaderColorChangeEvent> SHADER_COLOR_CHANGE = EventBus.create(ShaderColorChangeEvent.class);
|
||||
@@ -46,6 +47,7 @@ public final class GLSMHooks {
|
||||
public static final ProgramDeleteEvent programDeleteEvent = new ProgramDeleteEvent();
|
||||
public static final BlendFuncChangeEvent blendFuncChangeEvent = new BlendFuncChangeEvent();
|
||||
public static final FogStateChangeEvent fogStateChangeEvent = new FogStateChangeEvent();
|
||||
public static final ClipPlanesInUseEvent clipPlanesInUseEvent = new ClipPlanesInUseEvent();
|
||||
public static final LightmapCoordsEvent lightmapCoordsEvent = new LightmapCoordsEvent();
|
||||
public static final AlphaStateChangeEvent alphaStateChangeEvent = new AlphaStateChangeEvent();
|
||||
public static final ShaderColorChangeEvent shaderColorChangeEvent = new ShaderColorChangeEvent();
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package com.gtnewhorizons.angelica.glsm.hooks.events;
|
||||
|
||||
import net.minecraftforge.eventbus.api.event.MutableEvent;
|
||||
|
||||
public final class ClipPlanesInUseEvent extends MutableEvent {}
|
||||
@@ -152,11 +152,11 @@ public final class Tracy {
|
||||
return backend;
|
||||
}
|
||||
|
||||
private static final String ABI_SENTINEL = "sectionEnter";
|
||||
private static final String ABI_SENTINEL = "frameImage";
|
||||
|
||||
private static boolean implementsCurrentAbi(TracyBackend backend) {
|
||||
try {
|
||||
backend.getClass().getMethod(ABI_SENTINEL, int.class, String.class);
|
||||
backend.getClass().getMethod(ABI_SENTINEL, long.class, int.class, int.class, int.class, boolean.class);
|
||||
return true;
|
||||
} catch (NoSuchMethodException e) {
|
||||
LOGGER.warn("Tracy backend {} was built against an older TracyBackend interface; profiling disabled", backend.getClass().getName());
|
||||
@@ -337,6 +337,12 @@ public final class Tracy {
|
||||
if (ptr != 0) BACKEND.frameMark(ptr);
|
||||
}
|
||||
|
||||
/** Copies {@code rgba} before returning; width and height must be multiples of 4. */
|
||||
public static void frameImage(long rgba, int width, int height, int offset, boolean flip) {
|
||||
if (!ENABLED || SHUTTING_DOWN) return;
|
||||
BACKEND.frameImage(rgba, width, height, offset, flip);
|
||||
}
|
||||
|
||||
public static void plotInt(String name, long value) {
|
||||
plotInt(name, value, TracyBackend.PLOT_FORMAT_NUMBER);
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ public interface TracyBackend {
|
||||
|
||||
void frameMark();
|
||||
void frameMark(long namePtr);
|
||||
void frameImage(long rgba, int width, int height, int offset, boolean flip);
|
||||
|
||||
long internFrameName(String name);
|
||||
long internPlotName(String name, int format);
|
||||
|
||||
+15
@@ -1,9 +1,12 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording.commands;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage;
|
||||
import com.gtnewhorizons.angelica.glsm.GLContextState;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMBuffers;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMVertexInput;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
public final class IndexedDrawBatch {
|
||||
@@ -34,17 +37,29 @@ public final class IndexedDrawBatch {
|
||||
GLSMVertexInput.glBindVertexArray(v);
|
||||
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, sharedVBO);
|
||||
final int stride = layout.stride();
|
||||
int vertexFlags = 0;
|
||||
for (int i = 0; i < layout.locations.length; i++) {
|
||||
final int loc = layout.locations[i];
|
||||
GLSMVertexInput.glVertexAttribPointer(loc, layout.sizes[i], layout.types[i], layout.normalized[i], stride, layout.offset(i));
|
||||
GLSMVertexInput.glEnableVertexAttribArray(loc);
|
||||
vertexFlags |= vertexFlag(loc);
|
||||
}
|
||||
// glVertexAttribPointer doesn't set these, and the FFP shader only reads the inputs they name. Saved with the VAO.
|
||||
VAOManager.setCurrentVertexFlags(vertexFlags);
|
||||
GLSMBuffers.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, sharedEBO);
|
||||
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, prevVBO);
|
||||
vao = v;
|
||||
vaoCtx = glCtx;
|
||||
}
|
||||
|
||||
private static int vertexFlag(int location) {
|
||||
if (location == Usage.COLOR.getAttributeLocation()) return VertexFlags.COLOR_BIT;
|
||||
if (location == Usage.NORMAL.getAttributeLocation()) return VertexFlags.NORMAL_BIT;
|
||||
if (location == Usage.PRIMARY_UV.getAttributeLocation()) return VertexFlags.TEXTURE_BIT;
|
||||
if (location == Usage.SECONDARY_UV.getAttributeLocation()) return VertexFlags.BRIGHTNESS_BIT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
if (deleted) return;
|
||||
deleted = true;
|
||||
|
||||
@@ -59,7 +59,7 @@ final class GLSMOwners {
|
||||
"disableFog", "enableFog", "fogColor", "getFogColor", "getFogMode", "getFogState", "glFog", "glFogf",
|
||||
"glFogi"},
|
||||
{"com/gtnewhorizons/angelica/glsm/GLSMFramebuffer",
|
||||
"attachTextureForReading", "framebufferCompletenessIsMeaningful", "getDrawBuffer", "getDrawFramebuffer",
|
||||
"attachTextureForReading", "createScreenCapture", "framebufferCompletenessIsMeaningful", "getDrawBuffer", "getDrawFramebuffer",
|
||||
"getDrawFramebufferGeneration", "getReadFramebuffer", "glBindFramebuffer", "glBindRenderbuffer",
|
||||
"glBlitFramebuffer", "glCheckFramebufferStatus", "glClear", "glDeleteFramebuffers", "glDeleteRenderbuffers",
|
||||
"glDrawBuffer", "glDrawBuffers", "glFramebufferRenderbuffer", "glFramebufferTexture",
|
||||
@@ -106,22 +106,22 @@ final class GLSMOwners {
|
||||
"glGetIntegeri", "glGetIntegeri_v", "glGetQueryObjecti", "glGetQueryObjectui", "glGetQueryObjectui64",
|
||||
"glGetString", "glGetStringi", "glIsQuery", "glQueryCounter"},
|
||||
{"com/gtnewhorizons/angelica/glsm/GLSMRasterizer",
|
||||
"anyClipPlaneEnabled", "disableCull", "disableScissorTest", "enableCull", "enableScissorTest",
|
||||
"getAutoNormalState", "getClipPlaneState", "getClipPlaneStates", "getCullState", "getDitherState",
|
||||
"getEdgeFlagState", "getHintState", "getLineSmoothState", "getLineState", "getLineStippleState",
|
||||
"getMap1Color4State", "getMap1IndexState", "getMap1NormalState", "getMap1TextureCoord1State",
|
||||
"getMap1TextureCoord2State", "getMap1TextureCoord3State", "getMap1TextureCoord4State",
|
||||
"getMap1Vertex3State", "getMap1Vertex4State", "getMap2Color4State", "getMap2IndexState",
|
||||
"getMap2NormalState", "getMap2TextureCoord1State", "getMap2TextureCoord2State", "getMap2TextureCoord3State",
|
||||
"getMap2TextureCoord4State", "getMap2Vertex3State", "getMap2Vertex4State", "getMultisampleState",
|
||||
"getPointSmoothState", "getPointSpriteState", "getPointState", "getPolygonOffsetFillState",
|
||||
"getPolygonOffsetLineState", "getPolygonOffsetPointState", "getPolygonSmoothState", "getPolygonState",
|
||||
"getPolygonStippleState", "getSampleAlphaToCoverageState", "getSampleAlphaToOneState",
|
||||
"getSampleCoverageState", "getScissorState", "getScissorTest", "getShadeModelState", "getViewportState",
|
||||
"glClipPlane", "glCullFace", "glFrontFace", "glGetClipPlane", "glHint", "glLineStipple", "glLineWidth",
|
||||
"glPointParameter", "glPointParameterf", "glPointParameteri", "glPointSize", "glPolygonMode",
|
||||
"glPolygonOffset", "glPolygonOffsetClamp", "glPolygonStipple", "glSampleCoverage", "glScissor",
|
||||
"glShadeModel", "glViewport"},
|
||||
"anyClipPlaneEnabled", "clipEmulationActive", "disableCull", "disableScissorTest", "enableCull",
|
||||
"enableScissorTest", "getAutoNormalState", "getClipPlaneState", "getClipPlaneStates", "getCullState",
|
||||
"getDitherState", "getEdgeFlagState", "getHintState", "getLineSmoothState", "getLineState",
|
||||
"getLineStippleState", "getMap1Color4State", "getMap1IndexState", "getMap1NormalState",
|
||||
"getMap1TextureCoord1State", "getMap1TextureCoord2State", "getMap1TextureCoord3State",
|
||||
"getMap1TextureCoord4State", "getMap1Vertex3State", "getMap1Vertex4State", "getMap2Color4State",
|
||||
"getMap2IndexState", "getMap2NormalState", "getMap2TextureCoord1State", "getMap2TextureCoord2State",
|
||||
"getMap2TextureCoord3State", "getMap2TextureCoord4State", "getMap2Vertex3State", "getMap2Vertex4State",
|
||||
"getMultisampleState", "getPointSmoothState", "getPointSpriteState", "getPointState",
|
||||
"getPolygonOffsetFillState", "getPolygonOffsetLineState", "getPolygonOffsetPointState",
|
||||
"getPolygonSmoothState", "getPolygonState", "getPolygonStippleState", "getSampleAlphaToCoverageState",
|
||||
"getSampleAlphaToOneState", "getSampleCoverageState", "getScissorState", "getScissorTest",
|
||||
"getShadeModelState", "getViewportState", "glClipPlane", "glCullFace", "glFrontFace", "glGetClipPlane",
|
||||
"glHint", "glLineStipple", "glLineWidth", "glPointParameter", "glPointParameterf", "glPointParameteri",
|
||||
"glPointSize", "glPolygonMode", "glPolygonOffset", "glPolygonOffsetClamp", "glPolygonStipple",
|
||||
"glSampleCoverage", "glScissor", "glShadeModel", "glViewport"},
|
||||
{"com/gtnewhorizons/angelica/glsm/GLSMTexEnvGen",
|
||||
"glGetTexEnv", "glGetTexEnvf", "glGetTexEnvi", "glGetTexGen", "glGetTexGend", "glGetTexGenf",
|
||||
"glGetTexGeni", "glTexEnv", "glTexEnvf", "glTexEnvi", "glTexGen", "glTexGend", "glTexGenf", "glTexGeni"},
|
||||
@@ -129,7 +129,7 @@ final class GLSMOwners {
|
||||
"deferredTexturesAwaitingRelease", "onFrameEnd"},
|
||||
{"com/gtnewhorizons/angelica/glsm/GLSMTextures",
|
||||
"abandonTextureStaging", "beginTextureStaging", "commitTextureStaging", "disableTexture", "enableTexture",
|
||||
"forcePixelUnpackState", "getActiveTextureUnit", "getActiveTextureUnitForServerState",
|
||||
"forcePixelPackState", "forcePixelUnpackState", "getActiveTextureUnit", "getActiveTextureUnitForServerState",
|
||||
"getActiveTextureUnitStack", "getBoundTextureForServerState", "getClientActiveTextureUnit",
|
||||
"getImageUnitBinding", "getMaxBoundTextureUnit", "getSamplerBinding", "getSamplerUnitCount", "getTextures",
|
||||
"glActiveTexture", "glActiveTextureARB", "glBindImageTexture", "glBindImageTextures",
|
||||
@@ -142,7 +142,7 @@ final class GLSMOwners {
|
||||
"glTexImage1D", "glTexImage2D", "glTexImage3D", "glTexParameter", "glTexParameterf", "glTexParameteri",
|
||||
"glTexStorage2D", "glTexSubImage1D", "glTexSubImage2D", "glTexSubImage3D", "lookupTexParameteri",
|
||||
"markTextureAsAtlas", "remapTexClamp", "remapTexClampBuffer", "remapTexImageForGLES",
|
||||
"restorePixelUnpackState", "setActiveTexture", "setFilter", "setTextureSupersamplingSamples",
|
||||
"restorePixelPackState", "restorePixelUnpackState", "setActiveTexture", "setFilter", "setTextureSupersamplingSamples",
|
||||
"trackMaxBoundTextureUnit", "trimTextureStaging", "unbindAllSamplers", "updateTexParameterfCache",
|
||||
"updateTexParameteriCache"},
|
||||
{"com/gtnewhorizons/angelica/glsm/GLSMVertexInput",
|
||||
|
||||
+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;
|
||||
|
||||
@@ -23,8 +23,12 @@ public class BooleanState implements ISettableState<BooleanState> {
|
||||
this.ffpStateOnly = ffpStateOnly;
|
||||
}
|
||||
|
||||
protected boolean forwardsToBackend() {
|
||||
return !ffpStateOnly;
|
||||
}
|
||||
|
||||
public void applyToBackend() {
|
||||
if (ffpStateOnly) return;
|
||||
if (!forwardsToBackend()) return;
|
||||
if (enabled) {
|
||||
RENDER_BACKEND.enable(glCap);
|
||||
} else {
|
||||
@@ -51,7 +55,7 @@ public class BooleanState implements ISettableState<BooleanState> {
|
||||
stateUnknown = false;
|
||||
this.enabled = enabled;
|
||||
onEnabledChanged();
|
||||
if (!ffpStateOnly) {
|
||||
if (forwardsToBackend()) {
|
||||
if (enabled) {
|
||||
RENDER_BACKEND.enable(this.glCap);
|
||||
} else {
|
||||
|
||||
@@ -27,10 +27,16 @@ public class ClipPlaneState {
|
||||
dest[3] = tempPlane.w;
|
||||
}
|
||||
|
||||
/** Writes the 4-component eye-space plane equation into {@code buf} at its current position. */
|
||||
public void putEyePlane(int index, FloatBuffer buf) {
|
||||
final float[] p = eyePlanes[index];
|
||||
buf.put(p[0]).put(p[1]).put(p[2]).put(p[3]);
|
||||
// Vulkan and clip emulation have no per-distance enable: a disabled plane must read as distance 0.
|
||||
public void putEffectivePlanes(int enabledMask, FloatBuffer buf) {
|
||||
for (int i = 0; i < eyePlanes.length; i++) {
|
||||
if ((enabledMask & (1 << i)) != 0) {
|
||||
final float[] p = eyePlanes[i];
|
||||
buf.put(p[0]).put(p[1]).put(p[2]).put(p[3]);
|
||||
} else {
|
||||
buf.put(0f).put(0f).put(0f).put(0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void getEyePlane(int index, DoubleBuffer buf) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -40,7 +40,7 @@ class GLESCapsTest {
|
||||
};
|
||||
|
||||
private static boolean onGles(int cap) {
|
||||
return GLESCaps.isCapAllowed(cap, true, true);
|
||||
return GLESCaps.isCapAllowed(cap, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -60,25 +60,10 @@ class GLESCapsTest {
|
||||
@Test
|
||||
void desktopContextAllowsEveryCap() {
|
||||
for (int cap : DESKTOP_ONLY) {
|
||||
assertTrue(GLESCaps.isCapAllowed(cap, false, false), () -> "cap 0x" + Integer.toHexString(cap) + " is legal on desktop core and the gate must never suppress it there");
|
||||
assertTrue(GLESCaps.isCapAllowed(cap, false), () -> "cap 0x" + Integer.toHexString(cap) + " is legal on desktop core and the gate must never suppress it there");
|
||||
}
|
||||
for (int cap : VALID_ON_GLES) {
|
||||
assertTrue(GLESCaps.isCapAllowed(cap, false, false));
|
||||
assertTrue(GLESCaps.isCapAllowed(cap, false));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void clipPlanesFollowClipCullDistance() {
|
||||
for (int i = 0; i < GLStateManager.MAX_CLIP_PLANES; i++) {
|
||||
final int cap = GL11.GL_CLIP_PLANE0 + i;
|
||||
assertTrue(GLESCaps.isCapAllowed(cap, true, true), () -> "clip plane " + cap + " works where EXT_clip_cull_distance is present and must not be suppressed");
|
||||
assertFalse(GLESCaps.isCapAllowed(cap, true, false), () -> "clip plane " + cap + " is invalid on GLES without EXT_clip_cull_distance");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void capsOutsideTheClipPlaneRangeAreUnaffectedByClipCullDistance() {
|
||||
assertTrue(GLESCaps.isCapAllowed(GL11.GL_BLEND, true, false));
|
||||
assertTrue(GLESCaps.isCapAllowed(GL11.GL_CLIP_PLANE0 + GLStateManager.MAX_CLIP_PLANES, true, false));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.backend.ScreenCapture;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL21;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@GLCoreTest
|
||||
class GLScreenCapture_GLTest {
|
||||
|
||||
private static final int SRC_W = 64;
|
||||
private static final int SRC_H = 32;
|
||||
private static final int DST_W = 16;
|
||||
private static final int DST_H = 8;
|
||||
|
||||
private record Target(int texture, int framebuffer) {
|
||||
void delete() {
|
||||
GLSMFramebuffer.glDeleteFramebuffers(framebuffer);
|
||||
GLSMTextures.glDeleteTextures(texture);
|
||||
}
|
||||
}
|
||||
|
||||
private static Target quadrantTarget() {
|
||||
final int texture = GLSMTextures.glGenTextures();
|
||||
GLSMTextures.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
GLSMTextures.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, SRC_W, SRC_H, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
GLSMTextures.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
||||
final int framebuffer = GLSMFramebuffer.glGenFramebuffers();
|
||||
GLSMFramebuffer.glBindFramebuffer(GL30.GL_FRAMEBUFFER, framebuffer);
|
||||
GLSMFramebuffer.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, texture, 0);
|
||||
GlTestSupport.fillQuadrants(SRC_W, SRC_H);
|
||||
GLSMFramebuffer.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
|
||||
return new Target(texture, framebuffer);
|
||||
}
|
||||
|
||||
@Test
|
||||
void downscalesFramebufferQuadrantsBottomUp() {
|
||||
final Target target = quadrantTarget();
|
||||
final ScreenCapture capture = GLSMFramebuffer.createScreenCapture(DST_W, DST_H);
|
||||
try {
|
||||
assertTrue(capture.begin(target.framebuffer(), SRC_W, SRC_H, 42L));
|
||||
GL11.glFinish();
|
||||
assertTrue(capture.poll());
|
||||
assertEquals(42L, capture.tag());
|
||||
GlTestSupport.assertQuadrantsBottomUp(capture.address(), DST_W, DST_H);
|
||||
capture.release();
|
||||
} finally {
|
||||
capture.delete();
|
||||
target.delete();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void ringRefusesWhenFullAndReturnsInBeginOrder() {
|
||||
final Target target = quadrantTarget();
|
||||
final ScreenCapture capture = GLSMFramebuffer.createScreenCapture(DST_W, DST_H);
|
||||
try {
|
||||
for (int i = 0; i < ScreenCapture.RING; i++) assertTrue(capture.begin(target.framebuffer(), SRC_W, SRC_H, i), "capture " + i + " was not recorded");
|
||||
assertFalse(capture.begin(target.framebuffer(), SRC_W, SRC_H, 99L), "a full ring must refuse a new capture");
|
||||
GL11.glFinish();
|
||||
for (int i = 0; i < ScreenCapture.RING; i++) {
|
||||
assertTrue(capture.poll(), "capture " + i + " not ready after glFinish");
|
||||
assertEquals(i, capture.tag(), "captures must come back in begin order");
|
||||
capture.release();
|
||||
}
|
||||
assertFalse(capture.poll());
|
||||
assertTrue(capture.idle());
|
||||
assertTrue(capture.begin(target.framebuffer(), SRC_W, SRC_H, 100L), "a drained ring must accept captures again");
|
||||
} finally {
|
||||
capture.delete();
|
||||
target.delete();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void restoresBindingsAndPixelStore() {
|
||||
final Target target = quadrantTarget();
|
||||
final int readFbo = GLSMFramebuffer.glGenFramebuffers();
|
||||
final int drawFbo = GLSMFramebuffer.glGenFramebuffers();
|
||||
final int packBuffer = GLSMBuffers.glGenBuffers();
|
||||
GLSMFramebuffer.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, readFbo);
|
||||
GLSMFramebuffer.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, drawFbo);
|
||||
GLSMBuffers.glBindBuffer(GL21.GL_PIXEL_PACK_BUFFER, packBuffer);
|
||||
GLSMBuffers.glBufferData(GL21.GL_PIXEL_PACK_BUFFER, 16L, GL15.GL_STREAM_READ);
|
||||
GLSMTextures.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 1);
|
||||
GLSMEnable.glEnable(GL11.GL_SCISSOR_TEST);
|
||||
|
||||
final ScreenCapture capture = GLSMFramebuffer.createScreenCapture(DST_W, DST_H);
|
||||
try {
|
||||
assertTrue(capture.begin(target.framebuffer(), SRC_W, SRC_H, 1L));
|
||||
GL11.glFinish();
|
||||
assertTrue(capture.poll());
|
||||
capture.release();
|
||||
|
||||
assertEquals(readFbo, GLSMFramebuffer.getReadFramebuffer());
|
||||
assertEquals(drawFbo, GLSMFramebuffer.getDrawFramebuffer());
|
||||
assertEquals(packBuffer, GLStateManager.ctx().boundPixelPackBuffer);
|
||||
assertEquals(1, GLStateManager.ctx().pixelPackState.alignment());
|
||||
assertTrue(GLSMRasterizer.getScissorTest().isEnabled());
|
||||
assertEquals(readFbo, GL11.glGetInteger(GL30.GL_READ_FRAMEBUFFER_BINDING));
|
||||
assertEquals(drawFbo, GL11.glGetInteger(GL30.GL_DRAW_FRAMEBUFFER_BINDING));
|
||||
assertEquals(packBuffer, GL11.glGetInteger(GL21.GL_PIXEL_PACK_BUFFER_BINDING));
|
||||
assertEquals(1, GL11.glGetInteger(GL11.GL_PACK_ALIGNMENT));
|
||||
assertTrue(GL11.glIsEnabled(GL11.GL_SCISSOR_TEST));
|
||||
} finally {
|
||||
capture.delete();
|
||||
GLSMEnable.glDisable(GL11.GL_SCISSOR_TEST);
|
||||
GLSMTextures.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 4);
|
||||
GLSMBuffers.glBindBuffer(GL21.GL_PIXEL_PACK_BUFFER, 0);
|
||||
GLSMBuffers.glDeleteBuffers(packBuffer);
|
||||
GLSMFramebuffer.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
|
||||
GLSMFramebuffer.glDeleteFramebuffers(readFbo);
|
||||
GLSMFramebuffer.glDeleteFramebuffers(drawFbo);
|
||||
target.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLCoreTest;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMAttribStack;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMBuffers;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMCurrentAttribs;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMDraws;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMEnable;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMMatrices;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMRasterizer;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMVertexInput;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.ClipTestSupport;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
@GLCoreTest
|
||||
class ClipPlaneGLTest {
|
||||
|
||||
private static final int SIZE = 64;
|
||||
private static final int BACKGROUND = 0xFF000000;
|
||||
private static final int QUAD = 0xFF0000FF;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
FfpFixture.resetFfpState();
|
||||
FfpFixture.activateFfp();
|
||||
GLSMRasterizer.glViewport(0, 0, SIZE, SIZE);
|
||||
GLSMMatrices.glMatrixMode(GL11.GL_PROJECTION);
|
||||
GLSMMatrices.glLoadIdentity();
|
||||
GLSMMatrices.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
GLSMMatrices.glLoadIdentity();
|
||||
GLSMEnable.glDisable(GL11.GL_DEPTH_TEST);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
for (int i = 0; i < GLStateManager.MAX_CLIP_PLANES; i++) {
|
||||
GLSMEnable.glDisable(GL11.GL_CLIP_PLANE0 + i);
|
||||
}
|
||||
FfpFixture.resetFfpState();
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "emulated={0}")
|
||||
@ValueSource(booleans = {false, true})
|
||||
void disabledPlaneDoesNotClip(boolean emulated) {
|
||||
try (var ignored = ClipTestSupport.emulation(emulated)) {
|
||||
ClipTestSupport.setPlane(1, 0, 0, 0, -1);
|
||||
GLSMEnable.glEnable(GL11.GL_CLIP_PLANE1);
|
||||
GLSMEnable.glDisable(GL11.GL_CLIP_PLANE1);
|
||||
ClipTestSupport.setPlane(0, 1, 0, 0, 0);
|
||||
GLSMEnable.glEnable(GL11.GL_CLIP_PLANE0);
|
||||
assertRightHalfOnly(drawQuad(), "plane1 disabled, plane0 enabled");
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "emulated={0}")
|
||||
@ValueSource(booleans = {false, true})
|
||||
void attribPopRestoresClipping(boolean emulated) {
|
||||
try (var ignored = ClipTestSupport.emulation(emulated)) {
|
||||
ClipTestSupport.setPlane(0, 1, 0, 0, 0);
|
||||
GLSMEnable.glEnable(GL11.GL_CLIP_PLANE0);
|
||||
assertDriverClipPlane0(!emulated, "after glEnable");
|
||||
GLSMAttribStack.glPushAttrib(GL11.GL_ENABLE_BIT);
|
||||
GLSMEnable.glDisable(GL11.GL_CLIP_PLANE0);
|
||||
final int[] unclipped = drawQuad();
|
||||
GLSMAttribStack.glPopAttrib();
|
||||
assertDriverClipPlane0(!emulated, "after glPopAttrib");
|
||||
GLSMAttribStack.replayStateToBackend();
|
||||
assertDriverClipPlane0(!emulated, "after replayStateToBackend");
|
||||
final int[] restored = drawQuad();
|
||||
|
||||
for (int i = 0; i < unclipped.length; i++) {
|
||||
final int px = i;
|
||||
assertEquals(QUAD, unclipped[i], () -> "plane disabled inside the pushed scope, pixel " + px % SIZE + "," + px / SIZE);
|
||||
}
|
||||
assertRightHalfOnly(restored, "after glPopAttrib");
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertDriverClipPlane0(boolean expected, String when) {
|
||||
assertEquals(expected, GL11.glIsEnabled(GL11.GL_CLIP_PLANE0), () -> when + ": clip plane reaches the driver only when clip distances are native");
|
||||
}
|
||||
|
||||
private static void assertRightHalfOnly(int[] pixels, String what) {
|
||||
for (int i = 0; i < pixels.length; i++) {
|
||||
final int x = i % SIZE, y = i / SIZE;
|
||||
assertEquals(x < SIZE / 2 ? BACKGROUND : QUAD, pixels[i], () -> what + ": first mismatch at pixel " + x + "," + y);
|
||||
}
|
||||
}
|
||||
|
||||
private static int[] drawQuad() {
|
||||
final int vao = GLSMVertexInput.glGenVertexArrays();
|
||||
GLSMVertexInput.glBindVertexArray(vao);
|
||||
final int vbo = GLSMBuffers.glGenBuffers();
|
||||
try {
|
||||
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
|
||||
final FloatBuffer positions = BufferUtils.createFloatBuffer(18);
|
||||
positions.put(new float[] { -1, -1, 0, 1, -1, 0, 1, 1, 0, -1, -1, 0, 1, 1, 0, -1, 1, 0 }).flip();
|
||||
GLSMBuffers.glBufferData(GL15.GL_ARRAY_BUFFER, positions, GL15.GL_STATIC_DRAW);
|
||||
FfpFixture.attrib(0, 3, GL11.GL_FLOAT, false, 12, 0);
|
||||
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
VAOManager.setCurrentVertexFlags(0);
|
||||
|
||||
FfpFixture.clear();
|
||||
GLSMCurrentAttribs.glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
|
||||
GLSMDraws.glDrawArrays(GL11.GL_TRIANGLES, 0, 6);
|
||||
GLSMVertexInput.glBindVertexArray(0);
|
||||
return FfpFixture.readRegion(SIZE);
|
||||
} finally {
|
||||
GLSMBuffers.glDeleteBuffers(vbo);
|
||||
GLSMVertexInput.glDeleteVertexArrays(vao);
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
@@ -7,6 +7,7 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.lwjgl.PointerBuffer;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
import org.lwjgl.util.shaderc.Shaderc;
|
||||
@@ -55,6 +56,30 @@ class FFPUniformBlockSpirvLayoutTest {
|
||||
MemoryUtil.memFree(result.spirv());
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "emulated={0}")
|
||||
@ValueSource(booleans = {false, true})
|
||||
void clipDistanceCapabilityFollowsEmulation(boolean emulated) {
|
||||
long vbits = bit("BIT_CLIP_PLANES");
|
||||
if (emulated) vbits |= bit("BIT_CLIP_EMULATED");
|
||||
final String vs = VertexShaderGenerator.generate(VertexKey.fromPacked(vbits));
|
||||
assertEquals(!emulated, declaresClipDistance(vs, Shaderc.shaderc_vertex_shader), "vertex ClipDistance capability");
|
||||
}
|
||||
|
||||
private static boolean declaresClipDistance(String glsl, int shaderKind) {
|
||||
final SpirvCompiler.Result result = SpirvCompiler.compile(glsl, shaderKind, "clip-emulation-test", SpirvCompiler.Options.vulkanForced460Core());
|
||||
assertNotNull(result.spirv(), () -> "SPIR-V compile failed: " + result.error() + "\n" + glsl);
|
||||
try {
|
||||
final IntBuffer words = result.spirv().asIntBuffer();
|
||||
for (int i = 5; i < words.limit(); i += Math.max(1, words.get(i) >>> 16)) {
|
||||
final int word = words.get(i);
|
||||
if ((word & 0xFFFF) == 17 && words.get(i + 1) == 32) return true;
|
||||
}
|
||||
return false;
|
||||
} finally {
|
||||
MemoryUtil.memFree(result.spirv());
|
||||
}
|
||||
}
|
||||
|
||||
private static VertexKey cubeKey(boolean lightmapUnit, boolean unit0TextureMatrix, boolean texGen) {
|
||||
long bits = bit("BIT_HAS_VERTEX_COLOR") | bit("BIT_HAS_VERTEX_NORMAL") | bit("BIT_HAS_VERTEX_TEX") | bit("BIT_UNIT_TEX_BASE");
|
||||
if (lightmapUnit) bits |= 1L << (position("BIT_UNIT_TEX_BASE") + 1);
|
||||
|
||||
+74
-5
@@ -27,10 +27,12 @@ import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags.COLOR_BIT;
|
||||
import static com.gtnewhorizons.angelica.glsm.GlTestSupport.floats;
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
@@ -82,6 +84,7 @@ class FFPVertexLightingGLTest {
|
||||
GLSMEnable.glDisable(GL11.GL_LIGHT1);
|
||||
GLSMEnable.glDisable(GL11.GL_LIGHT2);
|
||||
GLSMEnable.glDisable(GL11.GL_COLOR_MATERIAL);
|
||||
GLSMCurrentAttribs.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
final ShaderManager sm = ShaderManager.getInstance();
|
||||
if (sm.isActive()) sm.deactivate();
|
||||
GLSMVertexInput.glBindVertexArray(0);
|
||||
@@ -126,7 +129,7 @@ class FFPVertexLightingGLTest {
|
||||
+ " light1=" + GLSMLighting.getLightStates()[1].isEnabled();
|
||||
}
|
||||
|
||||
private void buildTriangle() {
|
||||
private void buildTriangle(boolean withColor) {
|
||||
vao = GLSMVertexInput.glGenVertexArrays();
|
||||
GLSMVertexInput.glBindVertexArray(vao);
|
||||
|
||||
@@ -141,9 +144,13 @@ class FFPVertexLightingGLTest {
|
||||
GLSMBuffers.glBufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STATIC_DRAW);
|
||||
GLSMVertexInput.glEnableVertexAttribArray(POSITION_LOC);
|
||||
GLSMVertexInput.glVertexAttribPointer(POSITION_LOC, 3, GL11.GL_FLOAT, false, STRIDE, 0L);
|
||||
GLSMVertexInput.glEnableVertexAttribArray(COLOR_LOC);
|
||||
GLSMVertexInput.glVertexAttribPointer(COLOR_LOC, 4, GL11.GL_UNSIGNED_BYTE, true, STRIDE, 12L);
|
||||
VAOManager.setCurrentVertexFlags(COLOR_BIT);
|
||||
if (withColor) {
|
||||
GLSMVertexInput.glEnableVertexAttribArray(COLOR_LOC);
|
||||
GLSMVertexInput.glVertexAttribPointer(COLOR_LOC, 4, GL11.GL_UNSIGNED_BYTE, true, STRIDE, 12L);
|
||||
VAOManager.setCurrentVertexFlags(COLOR_BIT);
|
||||
} else {
|
||||
VAOManager.setCurrentVertexFlags(0);
|
||||
}
|
||||
}
|
||||
|
||||
private static void putVertex(ByteBuffer buf, float x, float y) {
|
||||
@@ -154,7 +161,11 @@ class FFPVertexLightingGLTest {
|
||||
}
|
||||
|
||||
private float[] drawAndReadCenter() {
|
||||
buildTriangle();
|
||||
return drawAndReadCenter(true);
|
||||
}
|
||||
|
||||
private float[] drawAndReadCenter(boolean withColor) {
|
||||
buildTriangle(withColor);
|
||||
final ShaderManager sm = ShaderManager.getInstance();
|
||||
ShaderManager.enable();
|
||||
sm.activate();
|
||||
@@ -233,6 +244,64 @@ class FFPVertexLightingGLTest {
|
||||
state.run();
|
||||
assertFalse(FFPVertexLighting.modulatesVertexColor(factor), reason);
|
||||
assertEquals(IDENTITY_FACTOR, factor, "a rejected solve must leave an identity factor");
|
||||
assertBakeRejected(reason);
|
||||
}
|
||||
|
||||
private static void assertBakeRejected(String reason) {
|
||||
final int[] raw = new int[8];
|
||||
Arrays.fill(raw, 0x5A3C1E0F);
|
||||
final int[] before = raw.clone();
|
||||
assertFalse(FFPVertexLighting.bakeColors(raw, 1, true, true), reason + ": bake must be rejected");
|
||||
assertArrayEquals(before, raw, reason + ": a rejected bake must leave the buffer untouched");
|
||||
}
|
||||
|
||||
@Test
|
||||
void positionalLightIsNotBaked() {
|
||||
GLSMLighting.glLight(GL11.GL_LIGHT0, GL11.GL_POSITION, floats(0.0f, 0.0f, 1.0f, 1.0f));
|
||||
assertBakeRejected("a positional light has a per-vertex direction");
|
||||
assertTrue(FFPVertexLighting.modulatesVertexColor(factor), "text keeps the object-origin approximation for positional lights");
|
||||
}
|
||||
|
||||
static Stream<Arguments> bakeCases() {
|
||||
return Stream.of(
|
||||
Arguments.of("flattened face-on", -1.0e-4f, 0.0f, 0.0f, 1.0f, true, true),
|
||||
Arguments.of("flattened top", -1.0e-4f, 0.0f, 1.0f, 0.0f, true, true),
|
||||
Arguments.of("flattened side", -1.0e-4f, 1.0f, 0.0f, 0.0f, true, true),
|
||||
Arguments.of("unflattened tilted", 1.0f, 0.3f, 0.8f, 0.5f, true, true),
|
||||
Arguments.of("current color", -1.0e-4f, 0.0f, 1.0f, 0.0f, true, false),
|
||||
Arguments.of("current normal", -1.0e-4f, 0.0f, 1.0f, 0.0f, false, true));
|
||||
}
|
||||
|
||||
private static int packNormal(float x, float y, float z) {
|
||||
return ((byte) (x * 127.0f) & 0xFF) | (((byte) (y * 127.0f) & 0xFF) << 8) | (((byte) (z * 127.0f) & 0xFF) << 16);
|
||||
}
|
||||
|
||||
private static float unpackNormal(int packed, int shift) {
|
||||
return (byte) (packed >> shift) / 127.0f;
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@MethodSource("bakeCases")
|
||||
void bakedColorMatchesGeneratedShader(String name, float zScale, float nx, float ny, float nz, boolean hasNormals, boolean hasColor) {
|
||||
GLSMMatrices.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
GLSMMatrices.glLoadIdentity();
|
||||
GLSMMatrices.glScalef(1.0f, 1.0f, zScale);
|
||||
standardItemLighting();
|
||||
if (!hasColor) GLSMCurrentAttribs.glColor4f(0.5f, 0.5f, 0.5f, 1.0f);
|
||||
|
||||
final int packedNormal = packNormal(nx, ny, nz);
|
||||
final int[] raw = new int[8];
|
||||
raw[5] = VERTEX_COLOR;
|
||||
if (hasNormals) raw[6] = packedNormal;
|
||||
GLSMCurrentAttribs.glNormal3f(unpackNormal(packedNormal, 0), unpackNormal(packedNormal, 8), unpackNormal(packedNormal, 16));
|
||||
assertTrue(FFPVertexLighting.bakeColors(raw, 1, hasNormals, hasColor), name + ": expected a bakeable state, " + gateState());
|
||||
|
||||
final float[] actual = drawAndReadCenter(hasColor);
|
||||
for (int c = 0; c < 3; c++) {
|
||||
final int baked = (raw[5] >>> (c * 8)) & 0xFF;
|
||||
final int shader = Math.round(actual[c] * 255.0f);
|
||||
assertEquals(baked, shader, name + ": channel " + c + " baked=" + baked + " shader=" + shader);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+8
-10
@@ -3,21 +3,21 @@ package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
import com.gtnewhorizons.angelica.glsm.GLCoreTest;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMCurrentAttribs;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMDepthStencilAlpha;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMEnable;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMFog;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMLighting;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMMatrices;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMRasterizer;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMTexEnvGen;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.ClipTestSupport;
|
||||
import org.junit.jupiter.api.DynamicTest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestFactory;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL31;
|
||||
|
||||
import java.nio.DoubleBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -32,12 +32,6 @@ class FfpUniformBlockDedupTest {
|
||||
return GL30.glGetInteger(GL31.GL_UNIFORM_BUFFER_START, FFPUniformBlock.BINDING_POINT);
|
||||
}
|
||||
|
||||
private static DoubleBuffer doubles(double... values) {
|
||||
final DoubleBuffer buf = BufferUtils.createDoubleBuffer(values.length);
|
||||
buf.put(values).flip();
|
||||
return buf;
|
||||
}
|
||||
|
||||
private static void resetState() {
|
||||
GLSMMatrices.glMatrixMode(GL11.GL_TEXTURE);
|
||||
GLSMMatrices.glLoadIdentity();
|
||||
@@ -58,7 +52,8 @@ class FfpUniformBlockDedupTest {
|
||||
GLSMLighting.glLight(GL11.GL_LIGHT0, GL11.GL_DIFFUSE, floats(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
GLSMLighting.glMaterialf(GL11.GL_FRONT, GL11.GL_SHININESS, 0.0f);
|
||||
GLSMTexEnvGen.glTexGen(GL11.GL_S, GL11.GL_OBJECT_PLANE, floats(1.0f, 0.0f, 0.0f, 0.0f));
|
||||
GLSMRasterizer.glClipPlane(GL11.GL_CLIP_PLANE0, doubles(0.0, 0.0, 0.0, 0.0));
|
||||
GLSMEnable.glDisable(GL11.GL_CLIP_PLANE0);
|
||||
ClipTestSupport.setPlane(0, 0.0, 0.0, 0.0, 0.0);
|
||||
GLSMRasterizer.glLineStipple(1, (short) 0xFFFF);
|
||||
GLSMRasterizer.glViewport(0, 0, 854, 480);
|
||||
}
|
||||
@@ -149,7 +144,10 @@ class FfpUniformBlockDedupTest {
|
||||
addCategory(tests, "currentTexCoord", () -> GLSMCurrentAttribs.glTexCoord2f(0.125f, 0.375f));
|
||||
addCategory(tests, "lightmapCoord", () -> GLStateManager.ctx().lastBrightnessX = 96.0f);
|
||||
addCategory(tests, "texGenPlane", () -> GLSMTexEnvGen.glTexGen(GL11.GL_S, GL11.GL_OBJECT_PLANE, floats(0.5f, 0.0f, 0.0f, 0.25f)));
|
||||
addCategory(tests, "clipPlane", () -> GLSMRasterizer.glClipPlane(GL11.GL_CLIP_PLANE0, doubles(0.0, 1.0, 0.0, 7.0)));
|
||||
addCategory(tests, "clipPlane", () -> {
|
||||
GLSMEnable.glEnable(GL11.GL_CLIP_PLANE0);
|
||||
ClipTestSupport.setPlane(0, 0.0, 1.0, 0.0, 7.0);
|
||||
});
|
||||
addCategory(tests, "alphaRef", () -> GLSMDepthStencilAlpha.glAlphaFunc(GL11.GL_GREATER, 0.35f));
|
||||
addCategory(tests, "fogColor", () -> GLSMFog.glFog(GL11.GL_FOG_COLOR, floats(0.1f, 0.2f, 0.3f, 0.4f)));
|
||||
addCategory(tests, "fogDensity", () -> GLSMFog.glFogf(GL11.GL_FOG_DENSITY, 0.75f));
|
||||
|
||||
+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,5 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
@@ -22,6 +23,35 @@ public final class GlTestSupport {
|
||||
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
|
||||
}
|
||||
|
||||
public static void fillQuadrants(int width, int height) {
|
||||
GLSMRasterizer.glViewport(0, 0, width, height);
|
||||
GLSMEnable.glEnable(GL11.GL_SCISSOR_TEST);
|
||||
clearRect(0, 0, width / 2, height / 2, 1f, 0f, 0f);
|
||||
clearRect(width / 2, 0, width / 2, height / 2, 0f, 1f, 0f);
|
||||
clearRect(0, height / 2, width / 2, height / 2, 0f, 0f, 1f);
|
||||
clearRect(width / 2, height / 2, width / 2, height / 2, 1f, 1f, 1f);
|
||||
GLSMEnable.glDisable(GL11.GL_SCISSOR_TEST);
|
||||
}
|
||||
|
||||
public static void assertQuadrantsBottomUp(long rgba, int width, int height) {
|
||||
assertRgba(rgba, width, width / 4, height / 4, 0xFF0000FF, "bottom-left");
|
||||
assertRgba(rgba, width, width * 3 / 4, height / 4, 0x00FF00FF, "bottom-right");
|
||||
assertRgba(rgba, width, width / 4, height * 3 / 4, 0x0000FFFF, "top-left");
|
||||
assertRgba(rgba, width, width * 3 / 4, height * 3 / 4, 0xFFFFFFFF, "top-right");
|
||||
}
|
||||
|
||||
private static void clearRect(int x, int y, int width, int height, float r, float g, float b) {
|
||||
GLSMRasterizer.glScissor(x, y, width, height);
|
||||
GLSMBlend.glClearColor(r, g, b, 1f);
|
||||
GLSMFramebuffer.glClear(GL11.GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
private static void assertRgba(long rgba, int width, int x, int y, int expected, String where) {
|
||||
final long at = rgba + ((long) y * width + x) * 4;
|
||||
final int actual = (MemoryUtilities.memGetByte(at) & 0xFF) << 24 | (MemoryUtilities.memGetByte(at + 1) & 0xFF) << 16 | (MemoryUtilities.memGetByte(at + 2) & 0xFF) << 8 | (MemoryUtilities.memGetByte(at + 3) & 0xFF);
|
||||
assertEquals(Integer.toHexString(expected), Integer.toHexString(actual), where + " quadrant at row " + y + " (rows bottom-up)");
|
||||
}
|
||||
|
||||
public static int compile(int type, String source) {
|
||||
final int shader = GLSMPrograms.glCreateShader(type);
|
||||
GLSMPrograms.glShaderSource(shader, source);
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package com.gtnewhorizons.angelica.glsm.testutil;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMRasterizer;
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.nio.DoubleBuffer;
|
||||
|
||||
public final class ClipTestSupport {
|
||||
|
||||
private ClipTestSupport() {}
|
||||
|
||||
public static void setPlane(int index, double a, double b, double c, double d) {
|
||||
final DoubleBuffer eq = BufferUtils.createDoubleBuffer(4);
|
||||
eq.put(a).put(b).put(c).put(d).flip();
|
||||
GLSMRasterizer.glClipPlane(GL11.GL_CLIP_PLANE0 + index, eq);
|
||||
}
|
||||
|
||||
public static Emulation emulation(boolean emulated) {
|
||||
return new Emulation(emulated);
|
||||
}
|
||||
|
||||
public static final class Emulation implements AutoCloseable {
|
||||
private final boolean previous;
|
||||
|
||||
private Emulation(boolean emulated) {
|
||||
previous = Reflect.getStatic(RenderSystem.class, "clipDistanceEmulated");
|
||||
Reflect.setStatic(RenderSystem.class, "clipDistanceEmulated", emulated);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
Reflect.setStatic(RenderSystem.class, "clipDistanceEmulated", previous);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) }
|
||||
|
||||
+67
-10
@@ -6,8 +6,13 @@ import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.DebugMessageHandler;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.MainThreadPump;
|
||||
import com.gtnewhorizons.retrofuturabootstrap.MainStartOnFirstThread;
|
||||
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;
|
||||
@@ -46,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;
|
||||
@@ -95,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() {
|
||||
@@ -251,7 +288,7 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
|
||||
public void finish() {GL11C.glFinish();}
|
||||
|
||||
private static boolean capAllowed(int cap) {
|
||||
return GLESCaps.isCapAllowed(cap, RenderSystem.isGLES(), RenderSystem.hasClipCullDistance());
|
||||
return GLESCaps.isCapAllowed(cap, RenderSystem.isGLES());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -563,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);
|
||||
}
|
||||
|
||||
@@ -603,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);
|
||||
}
|
||||
|
||||
@@ -684,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);
|
||||
}
|
||||
|
||||
@@ -704,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
|
||||
@@ -792,6 +844,11 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
|
||||
GL11C.glReadBuffer(mode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScreenCapture createScreenCapture(int width, int height) {
|
||||
return new GLScreenCapture(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readPixels(int x, int y, int width, int height, int format, int type, ByteBuffer pixels) {
|
||||
GL11C.glReadPixels(x, y, width, height, format, type, pixels);
|
||||
@@ -1364,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
|
||||
@@ -1434,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,234 @@
|
||||
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 com.gtnewhorizons.angelica.glsm.testutil.ClipTestSupport;
|
||||
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.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
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());
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "emulated={0}")
|
||||
@ValueSource(booleans = {false, true})
|
||||
void clipPlaneKeepsOnlyTheEnabledHalfSpace(boolean emulated) {
|
||||
try (var ignored = ClipTestSupport.emulation(emulated)) {
|
||||
try {
|
||||
bindClearedTarget();
|
||||
ClipTestSupport.setPlane(1, 0.0, 0.0, 0.0, -1.0);
|
||||
ClipTestSupport.setPlane(0, 1.0, 0.0, 0.0, 0.0);
|
||||
GLSMEnable.glEnable(GL30C.GL_CLIP_DISTANCE0);
|
||||
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(0xFF000000, readPixel(SIZE / 4, SIZE / 2), "x < 0 must be clipped by plane 0");
|
||||
assertEquals(0xFF00FF00, readPixel(3 * SIZE / 4, SIZE / 2), "x >= 0 must survive; plane 1 set but not enabled must not clip");
|
||||
assertEquals(GL11C.GL_NO_ERROR, GL11C.glGetError());
|
||||
} finally {
|
||||
GLSMEnable.glDisable(GL30C.GL_CLIP_DISTANCE0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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() {
|
||||
return readPixel(SIZE / 2, SIZE / 2);
|
||||
}
|
||||
|
||||
private static int readPixel(int x, int y) {
|
||||
final ByteBuffer pixel = MemoryUtil.memAlloc(4);
|
||||
try {
|
||||
GLSMFramebuffer.glReadPixels(x, y, 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
# Clip-plane scene for -Dangelica.debug.testBlocks=true. ~ is the flight start, yaw 0 faces south (+z).
|
||||
# Each angelica:test_clip_plane TESR keeps only the lower half of a gold cube (plane 0) and leaves a reject-all plane 1 disabled.
|
||||
|
||||
gamerule doMobSpawning false
|
||||
gamerule doDaylightCycle false
|
||||
time set 6000
|
||||
|
||||
fill ~-2 ~-1 ~4 ~2 ~0 ~4 angelica:test_clip_plane
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
+181
-105
@@ -18,7 +18,9 @@ import me.eigenraven.lwjgl3ify.client.MainThreadExec;
|
||||
import org.lwjglx.Lwjgl3ifyEventLoop;
|
||||
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;
|
||||
@@ -42,6 +44,7 @@ import com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineStore;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.pipeline.VertexAttribs;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.BufferParams;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.ByteRegionCopy;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.ColorSource;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.CopyRectClip;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.FBOClearTracker;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.FboState;
|
||||
@@ -111,6 +114,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import org.lwjgl.util.spvc.Spvc;
|
||||
import org.lwjgl.sdl.SDLGPU;
|
||||
import org.lwjgl.sdl.SDL_GPUBlitInfo;
|
||||
import org.lwjgl.sdl.SDLTimer;
|
||||
@@ -168,7 +172,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
new VoxelizationDispatcher(computeBinder);
|
||||
private final VertexAttribs vertexAttribs = new VertexAttribs();
|
||||
private final TextureOps textureOps = new TextureOps(device, frameManager, resourceManager, fboClearTracker);
|
||||
private final ReadbackShadows readbackShadows = new ReadbackShadows(device);
|
||||
private final ReadbackShadows readbackShadows = new ReadbackShadows(device, resourceManager, fenceTracker);
|
||||
private final PersistentBufferSync persistentSync = new PersistentBufferSync(frameManager, resourceManager,
|
||||
new PersistentBufferSync.UploadSink() {
|
||||
@Override public void enqueue(TransferThread.DeferredUpload upload) { enqueueUpload(upload); }
|
||||
@@ -1378,8 +1382,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
resourceManager.dropArrayShadow(dstGlId);
|
||||
resourceManager.dropEboShadow(dstGlId);
|
||||
resourceManager.markBufferContentsDefined(dstGlId);
|
||||
readbackShadows.invalidate(dstGlId, writeOffset);
|
||||
onBufferContentsWritten(dstGlId);
|
||||
|
||||
final ByteBuffer dstUboShadow = resourceManager.getUboShadow(dstGlId);
|
||||
if (dstUboShadow != null) {
|
||||
@@ -1460,9 +1463,8 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
final long cp = frameManager.ensureCopyPass();
|
||||
if (cp == 0) return;
|
||||
if (resourceManager.getBufferGlUsage(dstGlId) == GL15.GL_STREAM_READ
|
||||
&& readbackShadows.scheduleDownload(cp, srcHandle, readOffset, dstGlId, writeOffset, size)) {
|
||||
return;
|
||||
if (resourceManager.getBufferGlUsage(dstGlId) == GL15.GL_STREAM_READ && !isBoundAsSsbo(s(), dstGlId)) {
|
||||
readbackShadows.scheduleDownload(cp, srcHandle, readOffset, dstGlId, writeOffset, size);
|
||||
}
|
||||
try (var stack = MemoryStack.stackPush()) {
|
||||
final SDL_GPUBufferLocation src = SDL_GPUBufferLocation.calloc(stack).buffer(srcHandle).offset((int) readOffset);
|
||||
@@ -1507,52 +1509,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 +1774,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 +1935,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);
|
||||
@@ -2195,6 +2143,32 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
if (capacity < needed) throw new IllegalArgumentException("readback of " + width + "x" + height + " format 0x" + Integer.toHexString(format) + " type 0x" + Integer.toHexString(type) + " needs " + needed + " bytes, destination has " + capacity);
|
||||
}
|
||||
|
||||
@Override public ScreenCapture createScreenCapture(int width, int height) {
|
||||
return new SDLScreenCapture(this, device, frameManager, resourceManager, textureOps, width, height);
|
||||
}
|
||||
|
||||
ColorSource colorSourceForRead(int fboId) {
|
||||
final FboState fbo = fboId != 0 ? resourceManager.getFbo(fboId) : null;
|
||||
if (fbo == null) return null;
|
||||
final ContextState cs = s();
|
||||
final ColorSource source = resolveColorAttachment(fbo, cs.readColor);
|
||||
if (source.texture == 0L || source.width <= 0 || source.height <= 0) return null;
|
||||
fboClearTracker.materializePendingClearForRead(cs, frameManager.frame(), source.texture);
|
||||
return source;
|
||||
}
|
||||
|
||||
private ColorSource resolveColorAttachment(FboState fbo, ColorSource out) {
|
||||
final int colorGlId = fbo.colorGlIds[fbo.readBufferIndex];
|
||||
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(colorGlId);
|
||||
out.texture = resourceManager.getTextureHandle(colorGlId);
|
||||
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 ? TextureInfo.mipLevelSize(meta.width(), out.level) : fbo.width;
|
||||
out.height = meta != null ? TextureInfo.mipLevelSize(meta.height(), out.level) : fbo.height;
|
||||
return out;
|
||||
}
|
||||
|
||||
private void readPixelsInto(int x, int y, int width, int height, int format, int type, long dst, long capacity, int pbo, long pboOffset) {
|
||||
PixelPack.validate(format, type);
|
||||
if (width <= 0 || height <= 0) return;
|
||||
@@ -2224,14 +2198,13 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
final FboState fbo = resourceManager.getFbo(cs.boundReadFboId);
|
||||
if (fbo == null) return;
|
||||
if (kind == READBACK_COLOR) {
|
||||
final int colorGlId = fbo.colorGlIds[fbo.readBufferIndex];
|
||||
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(colorGlId);
|
||||
colorTex = resourceManager.getTextureHandle(colorGlId);
|
||||
colorFormat = meta != null ? meta.sdlFormat() : SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
|
||||
if (meta != null) colorBase = PixelPack.glBaseFormat(meta.glFormat());
|
||||
srcLevel = fbo.colorLevels[fbo.readBufferIndex];
|
||||
srcWidth = meta != null ? PixelOps.mipLevelSize(meta.width(), srcLevel) : fbo.width;
|
||||
srcHeight = meta != null ? PixelOps.mipLevelSize(meta.height(), srcLevel) : fbo.height;
|
||||
final ColorSource source = resolveColorAttachment(fbo, cs.readColor);
|
||||
colorTex = source.texture;
|
||||
colorFormat = source.sdlFormat;
|
||||
colorBase = source.glBaseFormat;
|
||||
srcLevel = source.level;
|
||||
srcWidth = source.width;
|
||||
srcHeight = source.height;
|
||||
} else {
|
||||
if (fbo.depthLevel != 0) throw new UnsupportedOperationException("glReadPixels of a depth/stencil attachment at level " + fbo.depthLevel + " is not supported on SDL-GPU");
|
||||
depthTex = fbo.depthTexture;
|
||||
@@ -2258,8 +2231,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);
|
||||
@@ -2348,7 +2321,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
} finally {
|
||||
region.clear();
|
||||
}
|
||||
resourceManager.markBufferContentsDefined(pbo);
|
||||
onBufferContentsWritten(pbo);
|
||||
flushAndSubmitMidFrame();
|
||||
}
|
||||
|
||||
@@ -2575,17 +2548,23 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
if (length != null && length.remaining() > 0) length.put(0, 0);
|
||||
return;
|
||||
}
|
||||
if (s == null) return;
|
||||
final byte[] bytes = s.getBytes(StandardCharsets.UTF_8);
|
||||
final int n = Math.min(bytes.length, out.remaining());
|
||||
out.put(bytes, 0, n);
|
||||
final byte[] bytes = (s == null ? "" : s).getBytes(StandardCharsets.UTF_8);
|
||||
final int pos = out.position();
|
||||
final int max = out.remaining();
|
||||
final int n = Math.max(0, Math.min(bytes.length, max - 1));
|
||||
for (int i = 0; i < n; i++) out.put(pos + i, bytes[i]);
|
||||
if (max > 0) out.put(pos + n, (byte) 0);
|
||||
if (length != null && length.remaining() > 0) length.put(0, n);
|
||||
}
|
||||
private static int glStringLength(String s) {
|
||||
return s == null || s.isEmpty() ? 0 : s.getBytes(StandardCharsets.UTF_8).length + 1;
|
||||
}
|
||||
@Override public int getShaderi(int shader, int pname) {
|
||||
return switch (pname) {
|
||||
case GL20.GL_COMPILE_STATUS -> shaderManager.isCompiled(shader) ? 1 : 0;
|
||||
case GL20.GL_SHADER_TYPE -> shaderManager.getShaderType(shader);
|
||||
case GL20.GL_INFO_LOG_LENGTH -> shaderManager.getShaderInfoLog(shader).length();
|
||||
case GL20.GL_INFO_LOG_LENGTH -> glStringLength(shaderManager.getShaderInfoLog(shader));
|
||||
case GL20.GL_SHADER_SOURCE_LENGTH -> glStringLength(shaderManager.getShaderSource(shader));
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
@@ -2593,7 +2572,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
return switch (pname) {
|
||||
case GL20.GL_LINK_STATUS -> shaderManager.isLinked(program) ? 1 : 0;
|
||||
case GL20.GL_VALIDATE_STATUS -> shaderManager.isLinked(program) ? 1 : 0;
|
||||
case GL20.GL_INFO_LOG_LENGTH -> shaderManager.getProgramInfoLog(program).length();
|
||||
case GL20.GL_INFO_LOG_LENGTH -> glStringLength(shaderManager.getProgramInfoLog(program));
|
||||
case GL20.GL_ATTACHED_SHADERS -> shaderManager.getAttachedShaderCount(program);
|
||||
case GL20.GL_ACTIVE_UNIFORMS -> {
|
||||
final var entries = getOrBuildUniformEntries(program);
|
||||
@@ -2633,6 +2612,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
final int loc = e.getValue();
|
||||
int glType;
|
||||
int arrayLen = 1;
|
||||
boolean array = false;
|
||||
if (prog.allSamplerNames.contains(name)) {
|
||||
glType = GL20.GL_SAMPLER_2D;
|
||||
} else {
|
||||
@@ -2641,6 +2621,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
if (umi != null) {
|
||||
glType = FormatMap.mapMemberInfoToGlType(umi);
|
||||
arrayLen = Math.max(1, umi.arrayLen());
|
||||
array = umi.arrayStride() > 0;
|
||||
} else {
|
||||
glType = GL20.GL_FLOAT_VEC4;
|
||||
}
|
||||
@@ -2648,7 +2629,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
glType = FormatMap.remapUnsignedIntToBool(glType);
|
||||
}
|
||||
}
|
||||
result.add(new UniformEntry(name, glType, arrayLen));
|
||||
result.add(new UniformEntry(array ? name + "[0]" : name, glType, arrayLen));
|
||||
}
|
||||
result.sort(Comparator.comparing(UniformEntry::name));
|
||||
final List<UniformEntry> immutable = Collections.unmodifiableList(result);
|
||||
@@ -2707,6 +2688,78 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
@Override public int getUniformLocation(int program, ByteBuffer name) {
|
||||
return shaderManager.getUniformLocation(program, MemoryUtil.memASCII(name));
|
||||
}
|
||||
@Override public void getUniformfv(int program, int location, FloatBuffer params) {
|
||||
final Readback rb = readback(program, location);
|
||||
if (rb == null) return;
|
||||
final int pos = params.position();
|
||||
final int count = Math.min(rb.count(), params.remaining());
|
||||
for (int i = 0; i < count; i++) {
|
||||
final float raw = rb.raw(i);
|
||||
final int bits = Float.floatToRawIntBits(raw);
|
||||
params.put(pos + i, switch (rb.type()) {
|
||||
case FLOAT -> raw;
|
||||
case INT -> (float) bits;
|
||||
case UINT -> (float) (bits & 0xFFFFFFFFL);
|
||||
case BOOL -> bits != 0 ? 1f : 0f;
|
||||
case OPAQUE -> rb.opaqueValue();
|
||||
});
|
||||
}
|
||||
}
|
||||
@Override public void getUniformiv(int program, int location, IntBuffer params) {
|
||||
final Readback rb = readback(program, location);
|
||||
if (rb == null) return;
|
||||
final int pos = params.position();
|
||||
final int count = Math.min(rb.count(), params.remaining());
|
||||
for (int i = 0; i < count; i++) {
|
||||
final float raw = rb.raw(i);
|
||||
final int bits = Float.floatToRawIntBits(raw);
|
||||
params.put(pos + i, switch (rb.type()) {
|
||||
case FLOAT -> roundHalfAwayFromZero(raw);
|
||||
case BOOL -> bits != 0 ? 1 : 0;
|
||||
case INT, UINT -> bits;
|
||||
case OPAQUE -> rb.opaqueValue();
|
||||
});
|
||||
}
|
||||
}
|
||||
private enum ReadType { FLOAT, INT, UINT, BOOL, OPAQUE }
|
||||
private record Readback(float[] storage, int start, int count, ReadType type, int opaqueValue) {
|
||||
float raw(int i) {
|
||||
return storage != null && start + i < storage.length ? storage[start + i] : 0f;
|
||||
}
|
||||
}
|
||||
private Readback readback(int program, int location) {
|
||||
final ShaderManager.ProgramObject prog = shaderManager.getProgram(program);
|
||||
if (prog == null || !prog.linked || location < 0 || location >= prog.uniformSlotCount) return null;
|
||||
final int kind = prog.locationKind[location];
|
||||
if (kind != ShaderManager.ProgramObject.LOCATION_KIND_PLAIN) {
|
||||
return new Readback(null, 0, 1, ReadType.OPAQUE, opaqueUnit(prog, location, kind));
|
||||
}
|
||||
final ShaderManager.UniformMemberInfo info = prog.memberInfo(location);
|
||||
if (info == null) return null;
|
||||
final int perElement = info.vectorSize() * info.columns();
|
||||
final int index = prog.arrayIndexBySlot[location];
|
||||
final int base = location - index;
|
||||
final float[] storage = s().uniformStaging(prog).uniformDataBySlot[base];
|
||||
return new Readback(storage, index * perElement, perElement, readType(prog, base, info), 0);
|
||||
}
|
||||
private static ReadType readType(ShaderManager.ProgramObject prog, int base, ShaderManager.UniformMemberInfo info) {
|
||||
if (info.baseType() == Spvc.SPVC_BASETYPE_FP32) return ReadType.FLOAT;
|
||||
if (info.baseType() != Spvc.SPVC_BASETYPE_UINT32) return ReadType.INT;
|
||||
final String name = prog.locationName[base];
|
||||
return name != null && prog.boolUniforms.contains(name) ? ReadType.BOOL : ReadType.UINT;
|
||||
}
|
||||
private static int roundHalfAwayFromZero(float f) {
|
||||
final double r = Math.floor(Math.abs((double) f) + 0.5);
|
||||
return (int) (f < 0 ? -r : r);
|
||||
}
|
||||
private static int opaqueUnit(ShaderManager.ProgramObject prog, int location, int kind) {
|
||||
final String name = prog.locationName[location];
|
||||
if (name == null) return 0;
|
||||
final int unit = kind == ShaderManager.ProgramObject.LOCATION_KIND_SAMPLER
|
||||
? prog.samplerTextureUnits.getInt(name)
|
||||
: prog.imageTextureUnits.getInt(name);
|
||||
return Math.max(unit, 0);
|
||||
}
|
||||
@Override public void getShaderSource(int shader, IntBuffer length, ByteBuffer source) {
|
||||
writeUtf8(shaderManager.getShaderSource(shader), length, source);
|
||||
}
|
||||
@@ -2955,7 +3008,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
case GL31.GL_COPY_WRITE_BUFFER -> st.boundCopyWriteBuffer = buffer;
|
||||
case GL43.GL_SHADER_STORAGE_BUFFER -> {
|
||||
st.boundSSBO = buffer;
|
||||
resourceManager.markBufferContentsDefined(buffer);
|
||||
onBufferContentsWritten(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3044,8 +3097,8 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
|
||||
private void respecifyStore(ContextState cs, int target, int glId, long size, int usage) {
|
||||
freePlainMappingFor(glId, true);
|
||||
resourceManager.recordBufferGlParams(glId, usage, BufferParams.MUTABLE_STORE);
|
||||
if (target == GL31.GL_UNIFORM_BUFFER) {
|
||||
resourceManager.recordBufferGlParams(glId, usage, BufferParams.MUTABLE_STORE);
|
||||
if (resourceManager.getBufferSize(glId) == size) return;
|
||||
if (size > 0) {
|
||||
resourceManager.recordBufferSizeOnly(glId, size);
|
||||
@@ -3060,6 +3113,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
readbackShadows.release(glId);
|
||||
resourceManager.deleteBuffer(glId);
|
||||
resourceManager.createBuffer(glId, sdlUsage, size);
|
||||
resourceManager.recordBufferGlParams(glId, usage, BufferParams.MUTABLE_STORE);
|
||||
if (target == GL15.GL_ARRAY_BUFFER) cs.bumpAttribStateGen();
|
||||
}
|
||||
|
||||
@@ -3099,8 +3153,8 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
private void respecifyStore(ContextState cs, int target, int glId, ByteBuffer data, int usage) {
|
||||
final long dataSize = data.remaining();
|
||||
freePlainMappingFor(glId, true);
|
||||
resourceManager.recordBufferGlParams(glId, usage, BufferParams.MUTABLE_STORE);
|
||||
if (target == GL31.GL_UNIFORM_BUFFER) {
|
||||
resourceManager.recordBufferGlParams(glId, usage, BufferParams.MUTABLE_STORE);
|
||||
if (dataSize > 0) {
|
||||
resourceManager.recordBufferSizeOnly(glId, dataSize);
|
||||
final ByteBuffer shadow = resourceManager.getOrAllocUboShadow(glId, (int) dataSize);
|
||||
@@ -3116,6 +3170,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
readbackShadows.release(glId);
|
||||
resourceManager.deleteBuffer(glId);
|
||||
final long handle = resourceManager.createBuffer(glId, sdlUsage, dataSize);
|
||||
resourceManager.recordBufferGlParams(glId, usage, BufferParams.MUTABLE_STORE);
|
||||
if (target == GL15.GL_ARRAY_BUFFER) cs.bumpAttribStateGen();
|
||||
if (target == GL15.GL_ELEMENT_ARRAY_BUFFER && dataSize > 0
|
||||
&& (cs.primitiveRestartEnabled || resourceManager.hasEboShadow(glId))) {
|
||||
@@ -3130,7 +3185,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
} else if (frameManager.getCommandBuffer() != 0) {
|
||||
resourceManager.uploadToBuffer(frameManager.ensureCopyPass(), data, handle, 0, false);
|
||||
}
|
||||
resourceManager.markBufferContentsDefined(glId);
|
||||
onBufferContentsWritten(glId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3220,7 +3275,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
FrameManager.warnDroppedOutsideFrame(droppedSsboWriteWarned, "bufferSubData(GL_SHADER_STORAGE_BUFFER)");
|
||||
droppedSsboWriteWarned = true;
|
||||
}
|
||||
resourceManager.markBufferContentsDefined(glId);
|
||||
onBufferContentsWritten(glId);
|
||||
return;
|
||||
}
|
||||
final boolean cycle = target == GL40.GL_DRAW_INDIRECT_BUFFER;
|
||||
@@ -3229,7 +3284,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
} else if (frameManager.getCommandBuffer() != 0) {
|
||||
resourceManager.uploadToBuffer(frameManager.ensureCopyPass(), data, handle, offset, cycle);
|
||||
}
|
||||
resourceManager.markBufferContentsDefined(glId);
|
||||
onBufferContentsWritten(glId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3294,7 +3349,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
} else if (frameManager.getCommandBuffer() != 0) {
|
||||
resourceManager.uploadToBuffer(frameManager.ensureCopyPass(), staging, gpuHandle, m.offset, false);
|
||||
}
|
||||
resourceManager.markBufferContentsDefined(glId);
|
||||
onBufferContentsWritten(glId);
|
||||
}
|
||||
final ByteBuffer old = resourceManager.putPboStaging(glId, staging);
|
||||
if (old != null) MemoryUtil.memFree(old);
|
||||
@@ -3308,7 +3363,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
shadow.position((int) m.offset).limit((int) (m.offset + m.length));
|
||||
shadow.put(staging);
|
||||
shadow.position(0).limit(shadow.capacity());
|
||||
resourceManager.markBufferContentsDefined(glId);
|
||||
onBufferContentsWritten(glId);
|
||||
cs.uboRangeGen++;
|
||||
}
|
||||
MemoryUtil.memFree(staging);
|
||||
@@ -3353,7 +3408,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
} else if (frameManager.getCommandBuffer() != 0) {
|
||||
resourceManager.uploadToBuffer(frameManager.ensureCopyPass(), staging, gpuHandle, m.offset, false);
|
||||
}
|
||||
resourceManager.markBufferContentsDefined(glId);
|
||||
onBufferContentsWritten(glId);
|
||||
}
|
||||
|
||||
MemoryUtil.memFree(staging);
|
||||
@@ -3408,6 +3463,10 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
MemoryUtil.memSet(staging, 0);
|
||||
return;
|
||||
}
|
||||
if (readbackShadows.serve(glId, offset, staging, len)) {
|
||||
staging.position(0).limit(len);
|
||||
return;
|
||||
}
|
||||
|
||||
final ByteBuffer direct = switch (target) {
|
||||
case GL30.GL_PIXEL_UNPACK_BUFFER -> resourceManager.getPboStaging(glId);
|
||||
@@ -3524,6 +3583,20 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
downloadBufferBlocking(handle, (int) offset, rem, data);
|
||||
}
|
||||
|
||||
private void onBufferContentsWritten(int glId) {
|
||||
resourceManager.markBufferContentsDefined(glId);
|
||||
readbackShadows.invalidateAll(glId);
|
||||
}
|
||||
|
||||
// An SSBO-bound buffer can be written by any later GPU pass, so it is never shadowed.
|
||||
private static boolean isBoundAsSsbo(ContextState st, int glId) {
|
||||
if (st.boundSSBO == glId) return true;
|
||||
for (int i = 0; i < ContextState.MAX_INDEXED_BUFFERS; i++) {
|
||||
if (st.boundSsboByIndex[i] == glId) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean readUboStore(int glId, long offset, ByteBuffer dst, int len) {
|
||||
final ByteBuffer shadow = resourceManager.getUboShadow(glId);
|
||||
if (shadow == null) return false;
|
||||
@@ -4042,6 +4115,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
if (refuseImmutable(buffer, "glNamedBufferData on immutable storage")) return;
|
||||
final int sdlUsage = FormatMap.mapBufferUsage(GL15.GL_ARRAY_BUFFER);
|
||||
freePlainMappingFor(buffer, true);
|
||||
readbackShadows.release(buffer);
|
||||
resourceManager.deleteBuffer(buffer);
|
||||
resourceManager.createBuffer(buffer, sdlUsage, size);
|
||||
resourceManager.recordBufferGlParams(buffer, usage, BufferParams.MUTABLE_STORE);
|
||||
@@ -4051,12 +4125,13 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
if (refuseImmutable(buffer, "glNamedBufferData on immutable storage")) return;
|
||||
final int sdlUsage = FormatMap.mapBufferUsage(GL15.GL_ARRAY_BUFFER);
|
||||
freePlainMappingFor(buffer, true);
|
||||
readbackShadows.release(buffer);
|
||||
resourceManager.deleteBuffer(buffer);
|
||||
final long handle = resourceManager.createBuffer(buffer, sdlUsage, data.remaining());
|
||||
resourceManager.recordBufferGlParams(buffer, usage, BufferParams.MUTABLE_STORE);
|
||||
if (handle != 0 && frameManager.getCommandBuffer() != 0) {
|
||||
resourceManager.uploadToBuffer(frameManager.ensureCopyPass(), data, handle, 0, false);
|
||||
resourceManager.markBufferContentsDefined(buffer);
|
||||
onBufferContentsWritten(buffer);
|
||||
}
|
||||
}
|
||||
@Override public void namedBufferData(int buffer, FloatBuffer data, int usage) {
|
||||
@@ -4067,7 +4142,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
final long handle = resourceManager.getBufferHandle(buffer);
|
||||
if (handle != 0 && frameManager.getCommandBuffer() != 0) {
|
||||
resourceManager.uploadToBuffer(frameManager.ensureCopyPass(), data, handle, offset, false);
|
||||
resourceManager.markBufferContentsDefined(buffer);
|
||||
onBufferContentsWritten(buffer);
|
||||
}
|
||||
}
|
||||
@Override public void copyTextureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int x, int y, int width, int height) {
|
||||
@@ -4119,8 +4194,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;
|
||||
@@ -4137,6 +4212,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
case GL20.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS -> 32;
|
||||
case GL20.GL_MAX_VERTEX_ATTRIBS -> 16;
|
||||
case GL20.GL_MAX_DRAW_BUFFERS -> 8;
|
||||
case GL30.GL_MAX_CLIP_DISTANCES -> device.hasClipDistance() ? GLStateManager.MAX_CLIP_PLANES : 0;
|
||||
case GL30.GL_MAX_COLOR_ATTACHMENTS -> 8;
|
||||
case GL15.GL_ARRAY_BUFFER_BINDING -> cs.boundArrayBuffer;
|
||||
case GL15.GL_ELEMENT_ARRAY_BUFFER_BINDING -> cs.currentVao.elementBuffer;
|
||||
@@ -4199,10 +4275,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;
|
||||
|
||||
@@ -4331,7 +4407,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
final int chunkBytes = (int) Math.min(size, defer ? CLEAR_CHUNK_BYTES : FILL_CHUNK_BYTES);
|
||||
final int alignedChunk = (chunkBytes / elemBytes) * elemBytes;
|
||||
if (alignedChunk == 0) return;
|
||||
resourceManager.markBufferContentsDefined(glId);
|
||||
onBufferContentsWritten(glId);
|
||||
final boolean zero = data == null || isAllZero(data, elemBytes);
|
||||
final ByteBuffer chunk = zero ? MemoryUtil.memCalloc(alignedChunk) : MemoryUtil.memAlloc(alignedChunk);
|
||||
try {
|
||||
@@ -4365,9 +4441,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;
|
||||
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.backend.ScreenCapture;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.device.Device;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.ColorSource;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.TextureOps;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import static org.lwjgl.sdl.SDLGPU.*;
|
||||
import static org.lwjgl.sdl.SDLSurface.SDL_FLIP_NONE;
|
||||
|
||||
final class SDLScreenCapture extends ScreenCapture {
|
||||
|
||||
private final SDLGPURenderBackend backend;
|
||||
private final Device device;
|
||||
private final FrameManager frameManager;
|
||||
private final ResourceManager resourceManager;
|
||||
private final TextureOps textureOps;
|
||||
|
||||
private final long[] textures = new long[RING];
|
||||
private final long[] transferBuffers = new long[RING];
|
||||
|
||||
SDLScreenCapture(SDLGPURenderBackend backend, Device device, FrameManager frameManager, ResourceManager resourceManager, TextureOps textureOps, int width, int height) {
|
||||
super(width, height);
|
||||
this.backend = backend;
|
||||
this.device = device;
|
||||
this.frameManager = frameManager;
|
||||
this.resourceManager = resourceManager;
|
||||
this.textureOps = textureOps;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean start(int slot, int srcFramebuffer, int srcWidth, int srcHeight) {
|
||||
if (frameManager.getCommandBuffer() == 0) return false;
|
||||
final ColorSource source = backend.colorSourceForRead(srcFramebuffer);
|
||||
if (source == null) return false;
|
||||
|
||||
final int w = width();
|
||||
final int h = height();
|
||||
if (textures[slot] == 0) {
|
||||
textures[slot] = resourceManager.createScratchTexture(SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM, w, h, SDL_GPU_TEXTUREUSAGE_COLOR_TARGET);
|
||||
}
|
||||
if (transferBuffers[slot] == 0) {
|
||||
transferBuffers[slot] = resourceManager.createTransferBuffer((long) w * h * 4, SDL_GPU_TRANSFERBUFFERUSAGE_DOWNLOAD);
|
||||
if (transferBuffers[slot] == 0) return false;
|
||||
}
|
||||
|
||||
textureOps.blitTexture(source.texture, source.level, 0, 0, Math.min(srcWidth, source.width), Math.min(srcHeight, source.height), textures[slot], 0, 0, 0, w, h, GL11.GL_LINEAR, SDL_FLIP_NONE);
|
||||
final long copyPass = frameManager.ensureCopyPass();
|
||||
if (copyPass == 0) return false;
|
||||
resourceManager.recordTextureDownload(copyPass, textures[slot], 0, 0, 0, 0, w, h, 1, transferBuffers[slot]);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long map(int slot) {
|
||||
return nSDL_MapGPUTransferBuffer(device.getDevice(), transferBuffers[slot], false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void unmap(int slot) {
|
||||
resourceManager.unmapTransferBuffer(transferBuffers[slot]);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deleteSlot(int slot) {
|
||||
resourceManager.releaseTextureDeferred(textures[slot]);
|
||||
resourceManager.releaseTransferBuffer(transferBuffers[slot]);
|
||||
textures[slot] = 0L;
|
||||
transferBuffers[slot] = 0L;
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,7 @@ public final class Device {
|
||||
|
||||
private long device;
|
||||
private boolean claimed;
|
||||
private boolean clipDistance = true;
|
||||
private int supportedShaderFormats;
|
||||
private String driverName;
|
||||
private boolean fencePollEnabled;
|
||||
@@ -116,6 +117,10 @@ public final class Device {
|
||||
}
|
||||
|
||||
public boolean createDevice() {
|
||||
return createDevice(!SystemProperties.EMULATE_CLIP_DISTANCES);
|
||||
}
|
||||
|
||||
boolean createDevice(boolean requestClipDistance) {
|
||||
if (device != 0) return true;
|
||||
|
||||
installLogCallback();
|
||||
@@ -130,9 +135,9 @@ public final class Device {
|
||||
}
|
||||
|
||||
final boolean gpuDebug = SystemProperties.LWJGL_DEBUG || SystemProperties.SDL_GPU_DEBUG;
|
||||
device = createGPUDevice(gpuDebug, candidates);
|
||||
device = createGPUDevice(gpuDebug, candidates, requestClipDistance);
|
||||
if (device == 0 && VideoDriverRecovery.shouldRetryOnX11(SDL_GetCurrentVideoDriver(), vulkanLoaderAvailable(), System.getenv("DISPLAY"))) {
|
||||
device = VideoDriverRecovery.retryUnderX11(() -> createGPUDevice(gpuDebug, candidates));
|
||||
device = VideoDriverRecovery.retryUnderX11(() -> createGPUDevice(gpuDebug, candidates, requestClipDistance));
|
||||
}
|
||||
if (device == 0) {
|
||||
logDeviceCreationFailure(REQUESTED_SHADER_FORMATS);
|
||||
@@ -206,13 +211,22 @@ public final class Device {
|
||||
}
|
||||
}
|
||||
|
||||
private long createGPUDevice(boolean gpuDebug, String[] candidates) {
|
||||
private long createGPUDevice(boolean gpuDebug, String[] candidates, boolean requestClipDistance) {
|
||||
final boolean macos = Platform.get() == Platform.MACOSX;
|
||||
for (final String driver : candidates) {
|
||||
final boolean macVulkan = macos && isVulkan(driver);
|
||||
if (macVulkan) useMoltenVk();
|
||||
final long dev = SDL_CreateGPUDevice(REQUESTED_SHADER_FORMATS, gpuDebug, driver);
|
||||
if (dev != 0) return dev;
|
||||
long dev = createWithProperties(gpuDebug, driver, requestClipDistance);
|
||||
boolean clip = requestClipDistance;
|
||||
if (dev == 0 && clip && isVulkan(driver)) {
|
||||
LOG.info("SDL GPU Vulkan device creation failed with shaderClipDistance ({}); the device may lack it, retrying without it, clip distances will be emulated", SDLError.SDL_GetError());
|
||||
dev = createWithProperties(gpuDebug, driver, false);
|
||||
clip = false;
|
||||
}
|
||||
if (dev != 0) {
|
||||
clipDistance = clip;
|
||||
return dev;
|
||||
}
|
||||
LOG.warn("Failed to create a '{}' SDL GPU device: {}", driver, SDLError.SDL_GetError());
|
||||
if (macVulkan && MoltenVK.locate() == null) {
|
||||
LOG.warn("Vulkan on macOS needs MoltenVK. Add {} to the mods folder or install MoltenVK.", MoltenVK.JAR_NAME);
|
||||
@@ -221,6 +235,22 @@ public final class Device {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static long createWithProperties(boolean gpuDebug, String driver, boolean clipDistance) {
|
||||
final int props = SDLProperties.SDL_CreateProperties();
|
||||
try {
|
||||
SDLProperties.SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_SPIRV_BOOLEAN, (REQUESTED_SHADER_FORMATS & SDL_GPU_SHADERFORMAT_SPIRV) != 0);
|
||||
SDLProperties.SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXBC_BOOLEAN, (REQUESTED_SHADER_FORMATS & SDL_GPU_SHADERFORMAT_DXBC) != 0);
|
||||
SDLProperties.SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXIL_BOOLEAN, (REQUESTED_SHADER_FORMATS & SDL_GPU_SHADERFORMAT_DXIL) != 0);
|
||||
SDLProperties.SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_MSL_BOOLEAN, (REQUESTED_SHADER_FORMATS & SDL_GPU_SHADERFORMAT_MSL) != 0);
|
||||
SDLProperties.SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_DEBUGMODE_BOOLEAN, gpuDebug);
|
||||
SDLProperties.SDL_SetStringProperty(props, SDL_PROP_GPU_DEVICE_CREATE_NAME_STRING, driver);
|
||||
SDLProperties.SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_CLIP_DISTANCE_BOOLEAN, clipDistance);
|
||||
return SDL_CreateGPUDeviceWithProperties(props);
|
||||
} finally {
|
||||
SDLProperties.SDL_DestroyProperties(props);
|
||||
}
|
||||
}
|
||||
|
||||
public void claimWindow(long window) {
|
||||
if (device == 0) {
|
||||
throw new IllegalStateException("SDL GPU device not created");
|
||||
@@ -298,6 +328,7 @@ public final class Device {
|
||||
driverVersion = null;
|
||||
driverInfo = null;
|
||||
supportedShaderFormats = 0;
|
||||
clipDistance = true;
|
||||
}
|
||||
|
||||
private void logWindowDiagnostics(long window) {
|
||||
@@ -351,6 +382,10 @@ public final class Device {
|
||||
return device;
|
||||
}
|
||||
|
||||
public boolean hasClipDistance() {
|
||||
return clipDistance;
|
||||
}
|
||||
|
||||
public String getDriverName() {
|
||||
return driverName;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
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;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.MappedRange;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.PackState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.LogicOpVariant;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.UniformStaging;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.longs.Long2FloatOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
||||
@@ -64,28 +67,10 @@ public final class ContextState {
|
||||
|
||||
public final PixelStoreState pixelStore = new PixelStoreState();
|
||||
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];
|
||||
@@ -199,6 +184,7 @@ public final class ContextState {
|
||||
private final Reference2ObjectOpenHashMap<ShaderManager.ProgramObject, UniformStaging> uniformStagingByProgram = new Reference2ObjectOpenHashMap<>();
|
||||
private ShaderManager.ProgramObject stagingProg;
|
||||
private UniformStaging currentUniformStaging;
|
||||
public final Int2ObjectOpenHashMap<float[]> uniformArrayScratch = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
public UniformStaging uniformStaging(ShaderManager.ProgramObject prog) {
|
||||
if (prog == stagingProg) return currentUniformStaging;
|
||||
|
||||
@@ -64,16 +64,20 @@ public final class FenceTracker {
|
||||
if (pendingFenceSyncs.isEmpty()) return;
|
||||
final long fence = frameManager.frame().lastAcquiredFence;
|
||||
frameManager.frame().lastAcquiredFence = 0;
|
||||
boolean fenceTaken = false;
|
||||
for (int i = 0; i < pendingFenceSyncs.size(); i++) {
|
||||
final long id = pendingFenceSyncs.getLong(i);
|
||||
if (!fenceMap.containsKey(id)) continue;
|
||||
if (fence != 0) {
|
||||
fenceMap.put(id, fence);
|
||||
fenceRefcounts.addTo(fence, 1);
|
||||
fenceTaken = true;
|
||||
} else {
|
||||
fenceMap.remove(id);
|
||||
}
|
||||
}
|
||||
pendingFenceSyncs.clear();
|
||||
if (fence != 0 && !fenceTaken && device.getDevice() != 0) device.fenceReleaser().release(fence);
|
||||
}
|
||||
|
||||
public int clientWaitSync(long sync, int flags, long timeout) {
|
||||
|
||||
+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;
|
||||
}
|
||||
}
|
||||
+92
-6
@@ -754,16 +754,32 @@ public final class PipelineApplier {
|
||||
public float[] reuseOrAlloc(ContextState st, int location, int length) {
|
||||
final ShaderManager.ProgramObject prog = st.boundProgramObj;
|
||||
if (prog == null || location < 0 || location >= prog.uniformSlotCount) return null;
|
||||
if (prog.floatLimitBySlot[location] < 0) return arrayScratch(st, length);
|
||||
final float[] existing = st.uniformStaging(prog).uniformDataBySlot[location];
|
||||
if (existing != null && existing.length == length) return existing;
|
||||
return new float[length];
|
||||
}
|
||||
|
||||
private static float[] arrayScratch(ContextState st, int length) {
|
||||
float[] scratch = st.uniformArrayScratch.get(length);
|
||||
if (scratch == null) {
|
||||
scratch = new float[length];
|
||||
st.uniformArrayScratch.put(length, scratch);
|
||||
}
|
||||
return scratch;
|
||||
}
|
||||
|
||||
public void putUniformFv(ContextState st, int location, FloatBuffer value) {
|
||||
final ShaderManager.ProgramObject prog = st.boundProgramObj;
|
||||
if (prog == null || location < 0 || location >= prog.uniformSlotCount) return;
|
||||
final int n = value.remaining();
|
||||
final int pos = value.position();
|
||||
if (prog.floatLimitBySlot[location] < 0) {
|
||||
final float[] v = arrayScratch(st, n);
|
||||
value.get(pos, v);
|
||||
putUniform(st, location, v);
|
||||
return;
|
||||
}
|
||||
final float[] existing = st.uniformStaging(prog).uniformDataBySlot[location];
|
||||
if (existing != null && existing.length == n) {
|
||||
if (contentEquals(existing, value, pos, n)) {
|
||||
@@ -824,6 +840,7 @@ public final class PipelineApplier {
|
||||
if (Tracy.ENABLED) frameManager.noteUniformBlockFlush(b, size);
|
||||
}
|
||||
|
||||
private static final long ARRAY_HASH_SEED = 0x9E3779B97F4A7C15L;
|
||||
private static final boolean VERIFY_PER_FRAME_BLOCK = SystemProperties.SDL_VERIFY_PER_FRAME_UNIFORM_BLOCK;
|
||||
private final HashSet<String> perFrameBlockDriftWarned = new HashSet<>();
|
||||
|
||||
@@ -837,6 +854,12 @@ public final class PipelineApplier {
|
||||
public void putUniform(ContextState st, int location, float[] data) {
|
||||
final ShaderManager.ProgramObject prog = st.boundProgramObj;
|
||||
if (prog == null || location < 0 || location >= prog.uniformSlotCount) return;
|
||||
final int limit = prog.floatLimitBySlot[location];
|
||||
if (limit < 0) {
|
||||
putArrayElement(st, prog, st.uniformStaging(prog), location - prog.arrayIndexBySlot[location], location, data);
|
||||
return;
|
||||
}
|
||||
if (data.length > limit) return;
|
||||
final UniformStaging us = st.uniformStaging(prog);
|
||||
final int len = data.length;
|
||||
final long valueKey;
|
||||
@@ -884,6 +907,62 @@ public final class PipelineApplier {
|
||||
}
|
||||
return;
|
||||
}
|
||||
writeStages(st, prog, us, location, data);
|
||||
}
|
||||
|
||||
private void putArrayElement(ContextState st, ShaderManager.ProgramObject prog, UniformStaging us, int base, int location, float[] data) {
|
||||
final ShaderManager.UniformMemberInfo baseInfo = prog.memberInfo(base);
|
||||
if (baseInfo == null) return;
|
||||
final int len = prog.arrayLenBySlot[location];
|
||||
final int index = prog.arrayIndexBySlot[location];
|
||||
final int count = Math.min(elementCount(baseInfo, data), len - index);
|
||||
if (count <= 0) return;
|
||||
final int floatsPerElement = baseInfo.vectorSize() * baseInfo.columns();
|
||||
if (Tracy.ENABLED) st.slotWrites++;
|
||||
final int start = index * floatsPerElement;
|
||||
final int floats = count * floatsPerElement;
|
||||
float[] storage = us.uniformDataBySlot[base];
|
||||
final boolean fresh = storage == null;
|
||||
long storageHash;
|
||||
if (fresh) {
|
||||
storage = us.uniformDataBySlot[base] = new float[len * floatsPerElement];
|
||||
storageHash = ARRAY_HASH_SEED;
|
||||
} else {
|
||||
storageHash = us.uniformValueHashBySlot[base];
|
||||
}
|
||||
boolean changed = fresh;
|
||||
for (int i = 0; i < floats; i++) {
|
||||
final int oldBits = Float.floatToRawIntBits(storage[start + i]);
|
||||
final int newBits = Float.floatToRawIntBits(data[i]);
|
||||
if (oldBits == newBits) continue;
|
||||
storageHash ^= arrayHashTerm(start + i, oldBits) ^ arrayHashTerm(start + i, newBits);
|
||||
storage[start + i] = data[i];
|
||||
changed = true;
|
||||
}
|
||||
if (!changed) {
|
||||
if (Tracy.ENABLED) st.slotWritesElided++;
|
||||
return;
|
||||
}
|
||||
us.uniformValueHashBySlot[base] = storageHash;
|
||||
for (int b = 0; b < ShaderManager.BLOCK_COUNT; b++) {
|
||||
final ShaderManager.UniformMemberInfo info = prog.blockInfoBySlot[b][location];
|
||||
if (info == null) continue;
|
||||
final ContextState.UniformBlockState block = st.uniformBlocks[b];
|
||||
if (prog.blockSize[b] > block.size) block.size = prog.blockSize[b];
|
||||
final FloatBuffer blockFb = block.staging(prog.blockSize[b]);
|
||||
final int hashSlot = block.hashSlot(prog.blockInfoBySlot[b][base].offset());
|
||||
if (hashSlot >= 0) block.setHash(hashSlot, storageHash);
|
||||
writeMember(st, prog, blockFb, prog.blockSize[b], info, data, location, false);
|
||||
block.dirty = true;
|
||||
if (VERIFY_PER_FRAME_BLOCK && b == ShaderManager.BLOCK_PER_FRAME && block.flushedThisFrame) {
|
||||
notePerFrameBlockDrift(prog, base);
|
||||
}
|
||||
return;
|
||||
}
|
||||
writeStages(st, prog, us, location, data);
|
||||
}
|
||||
|
||||
private void writeStages(ContextState st, ShaderManager.ProgramObject prog, UniformStaging us, int location, float[] data) {
|
||||
final ShaderManager.UniformMemberInfo vs = prog.vsInfoBySlot[location];
|
||||
if (vs != null && us.vsUniformFb != null) {
|
||||
writeMember(st, prog, us.vsUniformFb, prog.vertexUboSize, vs, data, location, true);
|
||||
@@ -896,6 +975,10 @@ public final class PipelineApplier {
|
||||
}
|
||||
}
|
||||
|
||||
private static long arrayHashTerm(int index, int bits) {
|
||||
return bits == 0 ? 0L : Hashing.fmix64(ARRAY_HASH_SEED, Hashing.packHiLo(index, bits));
|
||||
}
|
||||
|
||||
public void storeMatrix(ContextState st, int location, boolean transpose, FloatBuffer value, int size) {
|
||||
if (location < 0) return;
|
||||
final int n = value.remaining();
|
||||
@@ -914,16 +997,19 @@ public final class PipelineApplier {
|
||||
putUniform(st, location, out);
|
||||
}
|
||||
|
||||
private void writeMember(ContextState st, ShaderManager.ProgramObject prog, FloatBuffer ubo, int uboSize, ShaderManager.UniformMemberInfo info, float[] data, int loc, boolean isVs) {
|
||||
private static int elementCount(ShaderManager.UniformMemberInfo info, float[] data) {
|
||||
final int floatsPerElement = info.vectorSize() * info.columns();
|
||||
if (floatsPerElement <= 0) return;
|
||||
final int srcCount = data.length / floatsPerElement;
|
||||
if (srcCount <= 0) return;
|
||||
final int dataBytes = srcCount * floatsPerElement * 4;
|
||||
return floatsPerElement <= 0 ? 0 : data.length / floatsPerElement;
|
||||
}
|
||||
|
||||
private void writeMember(ContextState st, ShaderManager.ProgramObject prog, FloatBuffer ubo, int uboSize, ShaderManager.UniformMemberInfo info, float[] data, int loc, boolean isVs) {
|
||||
final int count = Math.min(elementCount(info, data), info.arrayLen());
|
||||
if (count <= 0) return;
|
||||
final int dataBytes = count * info.vectorSize() * info.columns() * 4;
|
||||
final int avail = uboSize - info.offset();
|
||||
if (dataBytes > avail) {
|
||||
throw new IllegalStateException("Uniform overflows UBO: prog=" + st.boundProgram + " loc=" + loc + " name='" + prog.locationToName.getOrDefault(loc, "?") + "' dataBytes=" + dataBytes + " availFromOffset=" + avail + " stage=" + (isVs ? "VS" : "FS"));
|
||||
}
|
||||
Std140Writer.write(ubo, info, data, srcCount);
|
||||
Std140Writer.write(ubo, info, data, count);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.resource;
|
||||
|
||||
public final class ColorSource {
|
||||
public long texture;
|
||||
public int level;
|
||||
public int width;
|
||||
public int height;
|
||||
public int sdlFormat;
|
||||
public int glBaseFormat;
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
+62
-45
@@ -1,16 +1,16 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.resource;
|
||||
|
||||
import com.gtnewhorizons.angelica.sdlgpu.device.Device;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.FenceTracker;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectIterator;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.lwjgl.sdl.SDLError;
|
||||
import org.lwjgl.sdl.SDL_GPUBufferRegion;
|
||||
import org.lwjgl.sdl.SDL_GPUTransferBufferCreateInfo;
|
||||
import org.lwjgl.sdl.SDL_GPUTransferBufferLocation;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
import org.lwjgl.opengl.GL32;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
@@ -25,25 +25,27 @@ public final class ReadbackShadows {
|
||||
private static final class Slot {
|
||||
long transferBuffer;
|
||||
int capacity;
|
||||
int validLen;
|
||||
boolean valid;
|
||||
long sync;
|
||||
}
|
||||
|
||||
private final Device device;
|
||||
private final Long2ObjectOpenHashMap<Slot> slots = new Long2ObjectOpenHashMap<>();
|
||||
private final ResourceManager resourceManager;
|
||||
private final FenceTracker fenceTracker;
|
||||
private final Int2ObjectOpenHashMap<Long2ObjectOpenHashMap<Slot>> byBuffer = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
public ReadbackShadows(Device device) {
|
||||
public ReadbackShadows(Device device, ResourceManager resourceManager, FenceTracker fenceTracker) {
|
||||
this.device = device;
|
||||
this.resourceManager = resourceManager;
|
||||
this.fenceTracker = fenceTracker;
|
||||
}
|
||||
|
||||
private static long key(int glId, long offset) {
|
||||
return ((long) glId << 32) | (offset & 0xFFFFFFFFL);
|
||||
}
|
||||
|
||||
public boolean scheduleDownload(long copyPass, long srcHandle, long srcOffset, int dstGlId, long dstOffset, long size) {
|
||||
if (copyPass == 0 || srcHandle == 0 || size <= 0 || size > Integer.MAX_VALUE) return false;
|
||||
public void scheduleDownload(long copyPass, long srcHandle, long srcOffset, int dstGlId, long dstOffset, long size) {
|
||||
if (copyPass == 0 || srcHandle == 0 || size <= 0 || size > Integer.MAX_VALUE) return;
|
||||
|
||||
final Slot slot = ensureSlot(dstGlId, dstOffset, (int) size);
|
||||
if (slot == null) return false;
|
||||
if (slot == null) return;
|
||||
|
||||
try (MemoryStack stack = stackPush()) {
|
||||
final SDL_GPUBufferRegion src = SDL_GPUBufferRegion.calloc(stack)
|
||||
@@ -55,18 +57,32 @@ public final class ReadbackShadows {
|
||||
.offset(0);
|
||||
SDL_DownloadFromGPUBuffer(copyPass, src, dst);
|
||||
}
|
||||
dropSync(slot);
|
||||
slot.sync = fenceTracker.fenceSync();
|
||||
slot.validLen = (int) size;
|
||||
slot.valid = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void invalidate(int glId, long offset) {
|
||||
final Slot slot = slots.get(key(glId, offset));
|
||||
if (slot != null) slot.valid = false;
|
||||
public void invalidateAll(int glId) {
|
||||
if (glId == 0) return;
|
||||
final Long2ObjectOpenHashMap<Slot> slots = byBuffer.get(glId);
|
||||
if (slots == null) return;
|
||||
for (Slot slot : slots.values()) {
|
||||
slot.valid = false;
|
||||
dropSync(slot);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean serve(int glId, long offset, ByteBuffer out, int len) {
|
||||
final Slot slot = slots.get(key(glId, offset));
|
||||
if (slot == null || !slot.valid || slot.transferBuffer == 0 || slot.capacity < len) return false;
|
||||
final Long2ObjectOpenHashMap<Slot> slots = byBuffer.get(glId);
|
||||
final Slot slot = slots != null ? slots.get(offset) : null;
|
||||
if (slot == null || !slot.valid || slot.transferBuffer == 0 || slot.validLen < len) return false;
|
||||
|
||||
if (slot.sync != 0) {
|
||||
final int wait = fenceTracker.clientWaitSync(slot.sync, GL32.GL_SYNC_FLUSH_COMMANDS_BIT, Long.MAX_VALUE);
|
||||
if (wait != GL32.GL_ALREADY_SIGNALED && wait != GL32.GL_CONDITION_SATISFIED) return false;
|
||||
dropSync(slot);
|
||||
}
|
||||
|
||||
final long mapped = nSDL_MapGPUTransferBuffer(device.getDevice(), slot.transferBuffer, false);
|
||||
if (mapped == 0) {
|
||||
@@ -80,62 +96,63 @@ public final class ReadbackShadows {
|
||||
}
|
||||
|
||||
public boolean hasSlotsFor(int glId) {
|
||||
for (long k : slots.keySet()) {
|
||||
if ((int) (k >>> 32) == glId) return true;
|
||||
}
|
||||
return false;
|
||||
return byBuffer.containsKey(glId);
|
||||
}
|
||||
|
||||
public void release(int glId) {
|
||||
if (slots.isEmpty()) return;
|
||||
final ObjectIterator<Long2ObjectMap.Entry<Slot>> it = slots.long2ObjectEntrySet().fastIterator();
|
||||
while (it.hasNext()) {
|
||||
final Long2ObjectMap.Entry<Slot> entry = it.next();
|
||||
if ((int) (entry.getLongKey() >>> 32) == glId) {
|
||||
releaseSlot(entry.getValue());
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
final Long2ObjectOpenHashMap<Slot> slots = byBuffer.remove(glId);
|
||||
if (slots == null) return;
|
||||
for (Slot slot : slots.values()) releaseSlot(slot);
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
for (Slot slot : slots.values()) releaseSlot(slot);
|
||||
slots.clear();
|
||||
for (Long2ObjectOpenHashMap<Slot> slots : byBuffer.values()) {
|
||||
for (Slot slot : slots.values()) releaseSlot(slot);
|
||||
}
|
||||
byBuffer.clear();
|
||||
}
|
||||
|
||||
private Slot ensureSlot(int glId, long offset, int size) {
|
||||
final long k = key(glId, offset);
|
||||
Slot slot = slots.get(k);
|
||||
Long2ObjectOpenHashMap<Slot> slots = byBuffer.get(glId);
|
||||
Slot slot = slots != null ? slots.get(offset) : null;
|
||||
if (slot != null && slot.capacity >= size) return slot;
|
||||
|
||||
if (slot == null) {
|
||||
if (slots == null) {
|
||||
slots = new Long2ObjectOpenHashMap<>();
|
||||
byBuffer.put(glId, slots);
|
||||
}
|
||||
slot = new Slot();
|
||||
slots.put(k, slot);
|
||||
slots.put(offset, slot);
|
||||
} else {
|
||||
releaseSlot(slot);
|
||||
}
|
||||
|
||||
try (MemoryStack stack = stackPush()) {
|
||||
final SDL_GPUTransferBufferCreateInfo ci = SDL_GPUTransferBufferCreateInfo.calloc(stack)
|
||||
.usage(SDL_GPU_TRANSFERBUFFERUSAGE_DOWNLOAD)
|
||||
.size(size);
|
||||
slot.transferBuffer = SDL_CreateGPUTransferBuffer(device.getDevice(), ci);
|
||||
}
|
||||
slot.transferBuffer = resourceManager.createTransferBuffer(size, SDL_GPU_TRANSFERBUFFERUSAGE_DOWNLOAD);
|
||||
if (slot.transferBuffer == 0) {
|
||||
LOG.error("Failed to create readback transfer buffer size={}: {}", size, SDLError.SDL_GetError());
|
||||
slots.remove(k);
|
||||
slots.remove(offset);
|
||||
if (slots.isEmpty()) byBuffer.remove(glId);
|
||||
return null;
|
||||
}
|
||||
slot.capacity = size;
|
||||
slot.validLen = 0;
|
||||
slot.valid = false;
|
||||
return slot;
|
||||
}
|
||||
|
||||
private void dropSync(Slot slot) {
|
||||
if (slot.sync == 0) return;
|
||||
fenceTracker.deleteSync(slot.sync);
|
||||
slot.sync = 0;
|
||||
}
|
||||
|
||||
private void releaseSlot(Slot slot) {
|
||||
dropSync(slot);
|
||||
if (slot.transferBuffer != 0 && device.getDevice() != 0) {
|
||||
SDL_ReleaseGPUTransferBuffer(device.getDevice(), slot.transferBuffer);
|
||||
resourceManager.releaseTransferBuffer(slot.transferBuffer);
|
||||
}
|
||||
slot.transferBuffer = 0;
|
||||
slot.capacity = 0;
|
||||
slot.validLen = 0;
|
||||
}
|
||||
}
|
||||
+45
-49
@@ -1572,9 +1572,13 @@ public final class ResourceManager {
|
||||
}
|
||||
|
||||
public long createTransferBuffer(long size) {
|
||||
return createTransferBuffer(size, SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD);
|
||||
}
|
||||
|
||||
public long createTransferBuffer(long size, int usage) {
|
||||
try (var stack = stackPush()) {
|
||||
final SDL_GPUTransferBufferCreateInfo ci = SDL_GPUTransferBufferCreateInfo.calloc(stack)
|
||||
.usage(SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD)
|
||||
.usage(usage)
|
||||
.size((int) size);
|
||||
|
||||
final long handle = SDL_CreateGPUTransferBuffer(device.getDevice(), ci);
|
||||
@@ -2067,23 +2071,42 @@ public final class ResourceManager {
|
||||
public void downloadFromTexture(long commandBuffer, long gpuTexture, int x, int y, int z, int w, int h, int d, int level, ByteBuffer output) {
|
||||
final int size = output.remaining();
|
||||
|
||||
final long xfer = createTransferBuffer(size, SDL_GPU_TRANSFERBUFFERUSAGE_DOWNLOAD);
|
||||
if (xfer == 0) return;
|
||||
|
||||
final long copyPass = SDL_BeginGPUCopyPass(commandBuffer);
|
||||
if (copyPass == 0) {
|
||||
LOG.error("Failed to begin copy pass for texture download: {}", SDLError.SDL_GetError());
|
||||
releaseTransferBuffer(xfer);
|
||||
return;
|
||||
}
|
||||
|
||||
recordTextureDownload(copyPass, gpuTexture, level, x, y, z, w, h, d, xfer);
|
||||
SDL_EndGPUCopyPass(copyPass);
|
||||
|
||||
final long fence = Submits.submitAndAcquireFence(commandBuffer);
|
||||
if (fence == 0) {
|
||||
LOG.error("Failed to submit download command buffer: {}", SDLError.SDL_GetError());
|
||||
releaseTransferBuffer(xfer);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
FenceWait.await(device, fence, FenceWait.FOREVER);
|
||||
} finally {
|
||||
device.fenceReleaser().release(fence);
|
||||
}
|
||||
|
||||
final long mappedPtr = nSDL_MapGPUTransferBuffer(device.getDevice(), xfer, false);
|
||||
if (mappedPtr != 0) {
|
||||
final ByteBuffer mapped = MemoryUtil.memByteBuffer(mappedPtr, size);
|
||||
output.put(mapped);
|
||||
}
|
||||
SDL_UnmapGPUTransferBuffer(device.getDevice(), xfer);
|
||||
releaseTransferBuffer(xfer);
|
||||
}
|
||||
|
||||
public void recordTextureDownload(long copyPass, long gpuTexture, int level, int x, int y, int z, int w, int h, int d, long xfer) {
|
||||
try (var stack = stackPush()) {
|
||||
final SDL_GPUTransferBufferCreateInfo xferCi = SDL_GPUTransferBufferCreateInfo.calloc(stack)
|
||||
.usage(SDL_GPU_TRANSFERBUFFERUSAGE_DOWNLOAD)
|
||||
.size(size);
|
||||
final long xfer = SDL_CreateGPUTransferBuffer(device.getDevice(), xferCi);
|
||||
if (xfer == 0) {
|
||||
LOG.error("Failed to create download transfer buffer: {}", SDLError.SDL_GetError());
|
||||
return;
|
||||
}
|
||||
|
||||
final long copyPass = SDL_BeginGPUCopyPass(commandBuffer);
|
||||
if (copyPass == 0) {
|
||||
LOG.error("Failed to begin copy pass for texture download: {}", SDLError.SDL_GetError());
|
||||
SDL_ReleaseGPUTransferBuffer(device.getDevice(), xfer);
|
||||
return;
|
||||
}
|
||||
|
||||
final SDL_GPUTextureRegion src = SDL_GPUTextureRegion.calloc(stack)
|
||||
.texture(gpuTexture)
|
||||
.mip_level(level)
|
||||
@@ -2095,45 +2118,18 @@ public final class ResourceManager {
|
||||
.offset(0);
|
||||
|
||||
SDL_DownloadFromGPUTexture(copyPass, src, dst);
|
||||
SDL_EndGPUCopyPass(copyPass);
|
||||
|
||||
final long fence = Submits.submitAndAcquireFence(commandBuffer);
|
||||
if (fence == 0) {
|
||||
LOG.error("Failed to submit download command buffer: {}", SDLError.SDL_GetError());
|
||||
SDL_ReleaseGPUTransferBuffer(device.getDevice(), xfer);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
FenceWait.await(device, fence, FenceWait.FOREVER);
|
||||
} finally {
|
||||
device.fenceReleaser().release(fence);
|
||||
}
|
||||
|
||||
final long mappedPtr = nSDL_MapGPUTransferBuffer(device.getDevice(), xfer, false);
|
||||
if (mappedPtr != 0) {
|
||||
final ByteBuffer mapped = MemoryUtil.memByteBuffer(mappedPtr, size);
|
||||
output.put(mapped);
|
||||
}
|
||||
SDL_UnmapGPUTransferBuffer(device.getDevice(), xfer);
|
||||
SDL_ReleaseGPUTransferBuffer(device.getDevice(), xfer);
|
||||
}
|
||||
}
|
||||
|
||||
public void downloadFromBuffer(long commandBuffer, long gpuBuffer, int offset, int size, ByteBuffer output) {
|
||||
try (var stack = stackPush()) {
|
||||
final SDL_GPUTransferBufferCreateInfo xferCi = SDL_GPUTransferBufferCreateInfo.calloc(stack)
|
||||
.usage(SDL_GPU_TRANSFERBUFFERUSAGE_DOWNLOAD)
|
||||
.size(size);
|
||||
final long xfer = SDL_CreateGPUTransferBuffer(device.getDevice(), xferCi);
|
||||
if (xfer == 0) {
|
||||
LOG.error("Failed to create download transfer buffer for buffer readback: {}", SDLError.SDL_GetError());
|
||||
return;
|
||||
}
|
||||
final long xfer = createTransferBuffer(size, SDL_GPU_TRANSFERBUFFERUSAGE_DOWNLOAD);
|
||||
if (xfer == 0) return;
|
||||
|
||||
final long copyPass = SDL_BeginGPUCopyPass(commandBuffer);
|
||||
if (copyPass == 0) {
|
||||
LOG.error("Failed to begin copy pass for buffer download: {}", SDLError.SDL_GetError());
|
||||
SDL_ReleaseGPUTransferBuffer(device.getDevice(), xfer);
|
||||
releaseTransferBuffer(xfer);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2152,7 +2148,7 @@ public final class ResourceManager {
|
||||
final long fence = Submits.submitAndAcquireFence(commandBuffer);
|
||||
if (fence == 0) {
|
||||
LOG.error("Failed to submit buffer download command buffer: {}", SDLError.SDL_GetError());
|
||||
SDL_ReleaseGPUTransferBuffer(device.getDevice(), xfer);
|
||||
releaseTransferBuffer(xfer);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -2167,7 +2163,7 @@ public final class ResourceManager {
|
||||
output.put(mapped);
|
||||
}
|
||||
SDL_UnmapGPUTransferBuffer(device.getDevice(), xfer);
|
||||
SDL_ReleaseGPUTransferBuffer(device.getDevice(), xfer);
|
||||
releaseTransferBuffer(xfer);
|
||||
}
|
||||
}
|
||||
private long defaultSampler;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -777,7 +777,7 @@ public final class ShaderManager {
|
||||
else prog.fragmentUboSize = refl.uboSize();
|
||||
}
|
||||
for (UboMember m : refl.uboMembers()) {
|
||||
final int loc = ensureLocation(prog, m.name());
|
||||
final int loc = ensureLocation(prog, m.name(), m.arrayLen());
|
||||
final UniformMemberInfo info = new UniformMemberInfo(
|
||||
m.offset(), m.size(), m.arrayStride(), isVertex,
|
||||
m.vectorSize(), m.columns(), m.baseType(), m.arrayLen()
|
||||
@@ -795,7 +795,7 @@ public final class ShaderManager {
|
||||
prog.blockSize[b] = Math.max(prog.blockSize[b], block.size());
|
||||
prog.blockBinding[b] = block.binding();
|
||||
for (UboMember m : block.members()) {
|
||||
final int loc = ensureLocation(prog, m.name());
|
||||
final int loc = ensureLocation(prog, m.name(), m.arrayLen());
|
||||
if (prog.blockMemberInfo[b].containsKey(loc)) continue;
|
||||
prog.blockMemberInfo[b].put(loc, new UniformMemberInfo(
|
||||
m.offset(), m.size(), m.arrayStride(), false,
|
||||
@@ -809,14 +809,34 @@ public final class ShaderManager {
|
||||
}
|
||||
|
||||
private static int ensureLocation(ProgramObject prog, String name) {
|
||||
return ensureLocation(prog, name, 1);
|
||||
}
|
||||
|
||||
private static int ensureLocation(ProgramObject prog, String name, int arrayLen) {
|
||||
final int existing = prog.nameToLocation.getInt(name);
|
||||
if (existing != -1) return existing;
|
||||
final int loc = prog.nextUniformLocation++;
|
||||
if (existing != -1) {
|
||||
final int registered = Math.max(1, prog.arrayLenByLocation.get(existing));
|
||||
if (registered == arrayLen) return existing;
|
||||
retireLocations(prog, existing, registered);
|
||||
}
|
||||
final int loc = prog.nextUniformLocation;
|
||||
prog.nextUniformLocation += arrayLen;
|
||||
prog.nameToLocation.put(name, loc);
|
||||
prog.locationToName.put(loc, name);
|
||||
for (int i = 1; i < arrayLen; i++) prog.locationToName.put(loc + i, name + "[" + i + "]");
|
||||
if (arrayLen > 1) prog.arrayLenByLocation.put(loc, arrayLen);
|
||||
return loc;
|
||||
}
|
||||
|
||||
private static void retireLocations(ProgramObject prog, int base, int len) {
|
||||
prog.arrayLenByLocation.remove(base);
|
||||
for (int i = 0; i < len; i++) prog.locationToName.remove(base + i);
|
||||
prog.locationToMemberInfo.remove(base);
|
||||
prog.vertexMemberInfo.remove(base);
|
||||
prog.fragmentMemberInfo.remove(base);
|
||||
for (int b = 0; b < BLOCK_COUNT; b++) prog.blockMemberInfo[b].remove(base);
|
||||
}
|
||||
|
||||
private static void applyVaryingMatch(ByteBuffer fragmentSpirv, StageReflection vs, StageReflection fs) {
|
||||
if (vs.vsOutputs().isEmpty() || fs.fsInputs().isEmpty() || fragmentSpirv == null) return;
|
||||
final HashMap<String, Integer> vsOutLocs = new HashMap<>(vs.vsOutputs().size() * 2);
|
||||
@@ -1268,7 +1288,13 @@ public final class ShaderManager {
|
||||
public int getUniformLocation(int program, String name) {
|
||||
final ProgramObject prog = programObjects.get(program);
|
||||
if (prog == null || !prog.linked) return -1;
|
||||
return prog.nameToLocation.getInt(name);
|
||||
final int exact = prog.nameToLocation.getInt(name);
|
||||
if (exact != -1) return exact;
|
||||
final int index = UniformNames.arrayIndex(name);
|
||||
if (index < 0) return -1;
|
||||
final int base = prog.nameToLocation.getInt(name.substring(0, name.lastIndexOf('[')));
|
||||
if (base < 0 || base >= prog.uniformSlotCount || index >= prog.arrayLenBySlot[base]) return -1;
|
||||
return base + index;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1645,7 +1671,12 @@ public final class ShaderManager {
|
||||
public record UniformMemberInfo(
|
||||
int offset, int size, int arrayStride, boolean isVertex,
|
||||
int vectorSize, int columns, int baseType, int arrayLen
|
||||
) {}
|
||||
) {
|
||||
public UniformMemberInfo element(int i) {
|
||||
return new UniformMemberInfo(offset + i * arrayStride, size - i * arrayStride, arrayStride, isVertex,
|
||||
vectorSize, columns, baseType, arrayLen - i);
|
||||
}
|
||||
}
|
||||
|
||||
public record UboMember(
|
||||
String name, int offset, int size, int arrayStride,
|
||||
@@ -2069,11 +2100,24 @@ public final class ShaderManager {
|
||||
return m != null ? m : blockMemberInfo[BLOCK_PER_PASS].get(loc);
|
||||
}
|
||||
|
||||
public UniformMemberInfo memberInfo(int slot) {
|
||||
for (int b = 0; b < BLOCK_COUNT; b++) {
|
||||
final UniformMemberInfo info = blockInfoBySlot[b][slot];
|
||||
if (info != null) return info;
|
||||
}
|
||||
final UniformMemberInfo vs = vsInfoBySlot[slot];
|
||||
return vs != null ? vs : fsInfoBySlot[slot];
|
||||
}
|
||||
|
||||
private static final UniformMemberInfo[] EMPTY_INFO = new UniformMemberInfo[0];
|
||||
|
||||
public UniformMemberInfo[] vsInfoBySlot;
|
||||
public UniformMemberInfo[] fsInfoBySlot;
|
||||
public int uniformSlotCount;
|
||||
public final Int2IntOpenHashMap arrayLenByLocation = new Int2IntOpenHashMap();
|
||||
public int[] arrayIndexBySlot = EMPTY_UNITS;
|
||||
public int[] arrayLenBySlot = EMPTY_UNITS;
|
||||
public int[] floatLimitBySlot = EMPTY_UNITS;
|
||||
public final int[] vertexInputBaseType = new int[16];
|
||||
public final int[] vertexInputVecSize = new int[16];
|
||||
public final String[] vertexInputName = new String[16];
|
||||
@@ -2200,24 +2244,51 @@ public final class ShaderManager {
|
||||
locationKind[loc] = LOCATION_KIND_PLAIN;
|
||||
}
|
||||
}
|
||||
arrayIndexBySlot = new int[n];
|
||||
arrayLenBySlot = new int[n];
|
||||
Arrays.fill(arrayLenBySlot, 1);
|
||||
for (var it = arrayLenByLocation.int2IntEntrySet().fastIterator(); it.hasNext(); ) {
|
||||
final var e = it.next();
|
||||
final int base = e.getIntKey();
|
||||
final int len = e.getIntValue();
|
||||
for (int i = 0; i < len; i++) {
|
||||
arrayLenBySlot[base + i] = len;
|
||||
arrayIndexBySlot[base + i] = i;
|
||||
}
|
||||
}
|
||||
for (int b = 0; b < BLOCK_COUNT; b++) {
|
||||
blockInfoBySlot[b] = new UniformMemberInfo[n];
|
||||
for (var it = blockMemberInfo[b].int2ObjectEntrySet().fastIterator(); it.hasNext(); ) {
|
||||
final var e = it.next();
|
||||
final int k = e.getIntKey();
|
||||
if (k >= 0 && k < n) blockInfoBySlot[b][k] = e.getValue();
|
||||
fillSlots(blockInfoBySlot[b], e.getIntKey(), e.getValue());
|
||||
}
|
||||
}
|
||||
for (var it = vertexMemberInfo.int2ObjectEntrySet().fastIterator(); it.hasNext(); ) {
|
||||
final var e = it.next();
|
||||
final int k = e.getIntKey();
|
||||
if (k >= 0 && k < n) vsInfoBySlot[k] = e.getValue();
|
||||
fillSlots(vsInfoBySlot, e.getIntKey(), e.getValue());
|
||||
}
|
||||
for (var it = fragmentMemberInfo.int2ObjectEntrySet().fastIterator(); it.hasNext(); ) {
|
||||
final var e = it.next();
|
||||
final int k = e.getIntKey();
|
||||
if (k >= 0 && k < n) fsInfoBySlot[k] = e.getValue();
|
||||
fillSlots(fsInfoBySlot, e.getIntKey(), e.getValue());
|
||||
}
|
||||
floatLimitBySlot = new int[n];
|
||||
for (int slot = 0; slot < n; slot++) {
|
||||
if (arrayLenBySlot[slot] > 1) {
|
||||
floatLimitBySlot[slot] = -1;
|
||||
continue;
|
||||
}
|
||||
final UniformMemberInfo info = memberInfo(slot);
|
||||
final int floats = info == null || info.arrayStride() > 0 ? 0 : info.vectorSize() * info.columns();
|
||||
floatLimitBySlot[slot] = floats > 0 ? floats : Integer.MAX_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
private void fillSlots(UniformMemberInfo[] bySlot, int base, UniformMemberInfo info) {
|
||||
final int n = bySlot.length;
|
||||
if (base < 0 || base >= n) return;
|
||||
bySlot[base] = info;
|
||||
final int len = arrayLenBySlot[base];
|
||||
for (int i = 1; i < len; i++) bySlot[base + i] = info.element(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.shader;
|
||||
|
||||
public final class UniformNames {
|
||||
|
||||
private UniformNames() {}
|
||||
|
||||
public static int arrayIndex(String name) {
|
||||
final int len = name.length();
|
||||
if (len == 0 || name.charAt(len - 1) != ']') return -1;
|
||||
int i = len - 1;
|
||||
while (i > 0 && isDigit(name.charAt(i - 1))) i--;
|
||||
if (i == 0 || name.charAt(i - 1) != '[') return -1;
|
||||
if (name.charAt(i) == '0' && i + 1 != len - 1) return -1;
|
||||
long index = 0;
|
||||
for (int c = i; c < len - 1; c++) {
|
||||
index = index * 10 + (name.charAt(c) - '0');
|
||||
if (index > Integer.MAX_VALUE) return Integer.MAX_VALUE;
|
||||
}
|
||||
return (int) index;
|
||||
}
|
||||
|
||||
private static boolean isDigit(char c) {
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
}
|
||||
+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
|
||||
|
||||
+10
@@ -2,11 +2,13 @@ package com.gtnewhorizons.angelica.sdlgpu.device;
|
||||
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assumptions;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@@ -46,4 +48,12 @@ class DeviceLifecycleGpuTest {
|
||||
device.destroyDevice();
|
||||
assertEquals(0L, device.getDevice());
|
||||
}
|
||||
|
||||
@Test
|
||||
void createsVulkanDeviceWithoutClipDistance() {
|
||||
device = new Device();
|
||||
assertTrue(device.createDevice(false), "no SDL GPU device on this machine");
|
||||
Assumptions.assumeTrue(device.isVulkan(), "test needs the vulkan driver, got " + device.getDriverName());
|
||||
assertFalse(device.hasClipDistance());
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import com.gtnewhorizons.angelica.glsm.ffp.CubeParityFixture;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.FfpFixture;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.Instancing;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.ParticleParityFixture;
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.ClipTestSupport;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
@@ -127,6 +128,30 @@ class GlsmSdlFfpTest {
|
||||
assertTexelEverywhere("two units, glCallLists");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {false, true})
|
||||
void disabledClipPlaneDoesNotClip(boolean emulated) {
|
||||
try (var ignored = ClipTestSupport.emulation(emulated)) {
|
||||
bindBaseTexture();
|
||||
try {
|
||||
ClipTestSupport.setPlane(1, 0.0, 0.0, 0.0, -1.0);
|
||||
GLSMEnable.glDisable(GL11.GL_CLIP_PLANE1);
|
||||
ClipTestSupport.setPlane(0, 1.0, 0.0, 0.0, 0.0);
|
||||
GLSMEnable.glEnable(GL11.GL_CLIP_PLANE0);
|
||||
quadUnit0Only();
|
||||
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
|
||||
final int left = pixelAt(pixels, SIZE, SIZE / 4, SIZE / 2);
|
||||
final int right = pixelAt(pixels, SIZE, 3 * SIZE / 4, SIZE / 2);
|
||||
assertTrue(left != TEXEL, () -> "left quarter must be clipped by enabled plane 0: " + describe(left));
|
||||
assertEquals(TEXEL, right, () -> "right quarter must survive; disabled plane 1 must not clip: " + describe(right));
|
||||
} finally {
|
||||
GLSMEnable.glDisable(GL11.GL_CLIP_PLANE0);
|
||||
GLSMEnable.glDisable(GL11.GL_CLIP_PLANE1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void litTexturedQuadIsShaded() {
|
||||
bindBaseTexture();
|
||||
|
||||
+215
@@ -3,6 +3,15 @@ package com.gtnewhorizons.angelica.sdlgpu.glsm;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMBuffers;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMFramebuffer;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMTextures;
|
||||
import com.gtnewhorizons.angelica.glsm.GlTestSupport;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.ScreenCapture;
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.FenceTracker;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.ReadbackShadows;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.SdlReflect;
|
||||
import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@@ -10,11 +19,19 @@ import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL31;
|
||||
import org.lwjgl.opengl.GL32;
|
||||
import org.lwjgl.opengl.GL43;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
|
||||
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.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@Tag("glsm-sdl")
|
||||
@ExtendWith(GlsmSdlFrameExtension.class)
|
||||
@@ -26,6 +43,14 @@ class GlsmSdlReadbackTest {
|
||||
private static final int B = 0x56;
|
||||
private static final int A = 0xFF;
|
||||
|
||||
private static final int SRC_W = 64;
|
||||
private static final int SRC_H = 32;
|
||||
private static final int CAPTURE_W = 16;
|
||||
private static final int CAPTURE_H = 8;
|
||||
private static final int COPY_BYTES = 64;
|
||||
|
||||
private record StreamReadCopy(int src, int dst, ByteBuffer data) {}
|
||||
|
||||
@Test
|
||||
void getTexImageSeesDeferredTextureUpload() {
|
||||
final int texture = GLSMTextures.glGenTextures();
|
||||
@@ -84,6 +109,196 @@ class GlsmSdlReadbackTest {
|
||||
assertEquals(0xFF, pixel.get(3) & 0xFF);
|
||||
}
|
||||
|
||||
@Test
|
||||
void screenCaptureSeesUserFramebuffer() {
|
||||
final int texture = GlsmSdlHeadlessRig.newNearestTexture();
|
||||
GLSMTextures.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, SRC_W, SRC_H, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
final int framebuffer = GlsmSdlHeadlessRig.fboWithColor(texture, 0);
|
||||
GlTestSupport.fillQuadrants(SRC_W, SRC_H);
|
||||
final ScreenCapture capture = GLSMFramebuffer.createScreenCapture(CAPTURE_W, CAPTURE_H);
|
||||
try {
|
||||
assertTrue(capture.begin(framebuffer, SRC_W, SRC_H, 7L), "capture was not recorded");
|
||||
GlsmSdlHeadlessRig.endFrame();
|
||||
GlsmSdlHeadlessRig.beginFrame();
|
||||
awaitPoll(capture);
|
||||
assertEquals(7L, capture.tag());
|
||||
GlTestSupport.assertQuadrantsBottomUp(capture.address(), CAPTURE_W, CAPTURE_H);
|
||||
capture.release();
|
||||
|
||||
final long texture0 = Reflect.<long[]>get(capture, "textures")[0];
|
||||
final long transferBuffer0 = Reflect.<long[]>get(capture, "transferBuffers")[0];
|
||||
assertNotEquals(0L, texture0, "slot 0 was never created");
|
||||
assertNotEquals(0L, transferBuffer0, "slot 0 was never created");
|
||||
capture.delete();
|
||||
GlsmSdlHeadlessRig.endFrame();
|
||||
GlsmSdlHeadlessRig.beginFrame();
|
||||
assertNotLive(texture0, "liveTextureHandles");
|
||||
assertNotLive(transferBuffer0, "liveTransferBufferHandles");
|
||||
} finally {
|
||||
capture.delete();
|
||||
GLSMFramebuffer.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
|
||||
GLSMFramebuffer.glDeleteFramebuffers(framebuffer);
|
||||
GLSMTextures.glDeleteTextures(texture);
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertNotLive(long handle, String field) {
|
||||
final LongOpenHashSet live = Reflect.get(GlsmSdlHeadlessRig.resourceManager(), field);
|
||||
assertFalse(live.contains(handle), field + " still holds capture handle " + handle + " after delete");
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteSyncBeforeSubmitIsNotResurrected() {
|
||||
final long sync = GLSMBuffers.glFenceSync(GL32.GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
GLSMBuffers.glDeleteSync(sync);
|
||||
GlsmSdlHeadlessRig.endFrame();
|
||||
GlsmSdlHeadlessRig.beginFrame();
|
||||
|
||||
final FenceTracker fences = Reflect.get(BackendManager.RENDER_BACKEND, "fenceTracker");
|
||||
final Long2LongOpenHashMap fenceMap = Reflect.get(fences, "fenceMap");
|
||||
assertFalse(fenceMap.containsKey(sync), "a sync deleted before its submit must not be bound to the frame fence afterwards");
|
||||
}
|
||||
|
||||
private static void awaitPoll(ScreenCapture capture) {
|
||||
final long deadline = System.nanoTime() + 5_000_000_000L;
|
||||
while (!capture.poll()) {
|
||||
assertTrue(System.nanoTime() < deadline, "capture did not complete within 5 s");
|
||||
LockSupport.parkNanos(1_000_000L);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void streamReadCopyServesWithoutAppFence() {
|
||||
GlsmSdlHeadlessRig.endFrame();
|
||||
final StreamReadCopy copy = streamReadCopy(false);
|
||||
assertTrue(hasShadow(copy.dst()), "copy into a GL_STREAM_READ buffer must take the shadow path");
|
||||
final ReadbackShadows shadows = Reflect.get(BackendManager.RENDER_BACKEND, "readbackShadows");
|
||||
assertNotEquals(0L, SdlReflect.readbackSlotSync(shadows, copy.dst(), 0), "the shadow download must carry a fence");
|
||||
|
||||
final ByteBuffer read = BufferUtils.createByteBuffer(COPY_BYTES);
|
||||
GLSMBuffers.glGetBufferSubData(GL31.GL_COPY_WRITE_BUFFER, 0, read);
|
||||
|
||||
assertBytes(copy.data(), read, "glGetBufferSubData right after the copy");
|
||||
assertEquals(0L, SdlReflect.readbackSlotSync(shadows, copy.dst(), 0), "the read must be served from the shadow, which consumes its fence");
|
||||
GLSMBuffers.glDeleteBuffers(copy.src());
|
||||
GLSMBuffers.glDeleteBuffers(copy.dst());
|
||||
}
|
||||
|
||||
@Test
|
||||
void streamReadShadowSkippedWhileBoundAsSsbo() {
|
||||
GlsmSdlHeadlessRig.endFrame();
|
||||
final StreamReadCopy copy = streamReadCopy(true);
|
||||
assertFalse(hasShadow(copy.dst()), "a destination bound as an SSBO can be written by the GPU, so it must not be shadowed");
|
||||
|
||||
final ByteBuffer read = BufferUtils.createByteBuffer(COPY_BYTES);
|
||||
GLSMBuffers.glGetBufferSubData(GL31.GL_COPY_WRITE_BUFFER, 0, read);
|
||||
assertBytes(copy.data(), read, "glGetBufferSubData of an SSBO-bound copy destination");
|
||||
GLSMBuffers.glBindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, 3, 0);
|
||||
GLSMBuffers.glDeleteBuffers(copy.src());
|
||||
GLSMBuffers.glDeleteBuffers(copy.dst());
|
||||
}
|
||||
|
||||
@Test
|
||||
void streamReadCopyVisibleToMapAndGpu() {
|
||||
GlsmSdlHeadlessRig.endFrame();
|
||||
final StreamReadCopy copy = streamReadCopy(false);
|
||||
|
||||
final ByteBuffer mapped = GLSMBuffers.glMapBufferRange(GL31.GL_COPY_WRITE_BUFFER, 0, COPY_BYTES, GL30.GL_MAP_READ_BIT);
|
||||
assertNotNull(mapped, "glMapBufferRange returned null");
|
||||
assertBytes(copy.data(), mapped, "glMapBufferRange READ of the copy destination");
|
||||
GLSMBuffers.glUnmapBuffer(GL31.GL_COPY_WRITE_BUFFER);
|
||||
|
||||
final int third = GLSMBuffers.glGenBuffers();
|
||||
GLSMBuffers.glBindBuffer(GL31.GL_COPY_READ_BUFFER, copy.dst());
|
||||
GLSMBuffers.glBindBuffer(GL31.GL_COPY_WRITE_BUFFER, third);
|
||||
GLSMBuffers.glBufferData(GL31.GL_COPY_WRITE_BUFFER, COPY_BYTES, GL15.GL_DYNAMIC_DRAW);
|
||||
GLSMBuffers.glCopyBufferSubData(GL31.GL_COPY_READ_BUFFER, GL31.GL_COPY_WRITE_BUFFER, 0, 0, COPY_BYTES);
|
||||
|
||||
final ByteBuffer read = BufferUtils.createByteBuffer(COPY_BYTES);
|
||||
GLSMBuffers.glGetBufferSubData(GL31.GL_COPY_WRITE_BUFFER, 0, read);
|
||||
assertBytes(copy.data(), read, "GPU copy out of the copy destination");
|
||||
|
||||
GLSMBuffers.glDeleteBuffers(third);
|
||||
GLSMBuffers.glDeleteBuffers(copy.src());
|
||||
GLSMBuffers.glDeleteBuffers(copy.dst());
|
||||
}
|
||||
|
||||
@Test
|
||||
void streamReadShadowDroppedOnSubData() {
|
||||
GlsmSdlHeadlessRig.endFrame();
|
||||
final StreamReadCopy copy = streamReadCopy(false);
|
||||
|
||||
final ByteBuffer other = BufferUtils.createByteBuffer(COPY_BYTES);
|
||||
for (int i = 0; i < COPY_BYTES; i++) other.put(i, (byte) (0xA0 ^ i));
|
||||
GLSMBuffers.glBufferSubData(GL31.GL_COPY_WRITE_BUFFER, 0, other);
|
||||
|
||||
final ByteBuffer read = BufferUtils.createByteBuffer(COPY_BYTES);
|
||||
GLSMBuffers.glGetBufferSubData(GL31.GL_COPY_WRITE_BUFFER, 0, read);
|
||||
|
||||
assertBytes(other, read, "glGetBufferSubData after glBufferSubData over the copy");
|
||||
GLSMBuffers.glDeleteBuffers(copy.src());
|
||||
GLSMBuffers.glDeleteBuffers(copy.dst());
|
||||
}
|
||||
|
||||
@Test
|
||||
void streamReadShadowServesOnlyDownloadedBytes() {
|
||||
GlsmSdlHeadlessRig.endFrame();
|
||||
final StreamReadCopy copy = streamReadCopy(false);
|
||||
final int half = COPY_BYTES / 2;
|
||||
final int quarter = COPY_BYTES / 4;
|
||||
|
||||
final ByteBuffer tail = BufferUtils.createByteBuffer(half);
|
||||
for (int i = 0; i < half; i++) tail.put(i, (byte) (0x50 + i));
|
||||
GLSMBuffers.glBufferSubData(GL31.GL_COPY_WRITE_BUFFER, half, tail);
|
||||
|
||||
final ByteBuffer head = BufferUtils.createByteBuffer(quarter);
|
||||
for (int i = 0; i < quarter; i++) head.put(i, (byte) (0xC0 + i));
|
||||
final int src2 = GLSMBuffers.glGenBuffers();
|
||||
GLSMBuffers.glBindBuffer(GL43.GL_SHADER_STORAGE_BUFFER, src2);
|
||||
GLSMBuffers.glBufferData(GL43.GL_SHADER_STORAGE_BUFFER, head, GL15.GL_DYNAMIC_DRAW);
|
||||
GLSMBuffers.glBindBuffer(GL31.GL_COPY_READ_BUFFER, src2);
|
||||
GLSMBuffers.glCopyBufferSubData(GL31.GL_COPY_READ_BUFFER, GL31.GL_COPY_WRITE_BUFFER, 0, 0, quarter);
|
||||
|
||||
final ByteBuffer read = BufferUtils.createByteBuffer(COPY_BYTES);
|
||||
GLSMBuffers.glGetBufferSubData(GL31.GL_COPY_WRITE_BUFFER, 0, read);
|
||||
|
||||
for (int i = 0; i < COPY_BYTES; i++) {
|
||||
final byte expected = i < quarter ? head.get(i) : i < half ? copy.data().get(i) : tail.get(i - half);
|
||||
assertEquals(expected, read.get(i), "byte " + i + " after a shorter copy reused a larger shadow");
|
||||
}
|
||||
GLSMBuffers.glDeleteBuffers(src2);
|
||||
GLSMBuffers.glDeleteBuffers(copy.src());
|
||||
GLSMBuffers.glDeleteBuffers(copy.dst());
|
||||
}
|
||||
|
||||
private static StreamReadCopy streamReadCopy(boolean dstBoundAsSsbo) {
|
||||
final ByteBuffer data = BufferUtils.createByteBuffer(COPY_BYTES);
|
||||
for (int i = 0; i < COPY_BYTES; i++) data.put(i, (byte) (i * 7 + 3));
|
||||
|
||||
final int src = GLSMBuffers.glGenBuffers();
|
||||
GLSMBuffers.glBindBuffer(GL43.GL_SHADER_STORAGE_BUFFER, src);
|
||||
GLSMBuffers.glBufferData(GL43.GL_SHADER_STORAGE_BUFFER, data, GL15.GL_DYNAMIC_DRAW);
|
||||
|
||||
final int dst = GLSMBuffers.glGenBuffers();
|
||||
GLSMBuffers.glBindBuffer(GL31.GL_COPY_READ_BUFFER, src);
|
||||
GLSMBuffers.glBindBuffer(GL31.GL_COPY_WRITE_BUFFER, dst);
|
||||
GLSMBuffers.glBufferData(GL31.GL_COPY_WRITE_BUFFER, COPY_BYTES, GL15.GL_STREAM_READ);
|
||||
if (dstBoundAsSsbo) GLSMBuffers.glBindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, 3, dst);
|
||||
GLSMBuffers.glCopyBufferSubData(GL31.GL_COPY_READ_BUFFER, GL31.GL_COPY_WRITE_BUFFER, 0, 0, COPY_BYTES);
|
||||
return new StreamReadCopy(src, dst, data);
|
||||
}
|
||||
|
||||
private static boolean hasShadow(int glId) {
|
||||
final ReadbackShadows shadows = Reflect.get(BackendManager.RENDER_BACKEND, "readbackShadows");
|
||||
return shadows.hasSlotsFor(glId);
|
||||
}
|
||||
|
||||
private static void assertBytes(ByteBuffer expected, ByteBuffer actual, String what) {
|
||||
for (int i = 0; i < COPY_BYTES; i++) {
|
||||
assertEquals(expected.get(i), actual.get(i), what + ": byte " + i);
|
||||
}
|
||||
}
|
||||
|
||||
private static ByteBuffer solidTexture(int width, int height) {
|
||||
final ByteBuffer pixels = BufferUtils.createByteBuffer(width * height * 4);
|
||||
for (int i = 0; i < width * height; i++) {
|
||||
|
||||
+246
@@ -0,0 +1,246 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMDraws;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMPrograms;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMVertexInput;
|
||||
import com.gtnewhorizons.angelica.glsm.GlTestSupport;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
|
||||
@Tag("glsm-sdl")
|
||||
@ExtendWith(GlsmSdlFrameExtension.class)
|
||||
class GlsmSdlUniformArrayTest {
|
||||
|
||||
private static final int COUNT = 8;
|
||||
private static final int[][] A = {
|
||||
{10, 20, 30}, {40, 50, 60}, {70, 80, 90}, {100, 110, 120},
|
||||
{130, 140, 150}, {160, 170, 180}, {190, 200, 210}, {220, 230, 240}
|
||||
};
|
||||
private static final int[] X = {250, 5, 15};
|
||||
private static final int[][] B = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {11, 12, 13}};
|
||||
|
||||
private int program;
|
||||
private int vao;
|
||||
private int arr;
|
||||
private int single;
|
||||
private int pick;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
program = GlTestSupport.program(GlsmSdlHeadlessRig.FULLSCREEN_VERTEX, """
|
||||
#version 330 core
|
||||
uniform vec4 arr[8];
|
||||
uniform vec4 single;
|
||||
uniform ivec4 iarr[2];
|
||||
uniform vec4 one[1];
|
||||
uniform int pick;
|
||||
out vec4 color;
|
||||
void main() { color = pick == -2 ? vec4(iarr[1]) / 255.0 : pick == -3 ? one[0] : pick < 0 ? single : arr[pick]; }
|
||||
""");
|
||||
vao = GLSMVertexInput.glGenVertexArrays();
|
||||
GLSMVertexInput.glBindVertexArray(vao);
|
||||
GLSMPrograms.glUseProgram(program);
|
||||
arr = loc("arr");
|
||||
single = loc("single");
|
||||
pick = loc("pick");
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
GLSMPrograms.glUseProgram(0);
|
||||
GLSMVertexInput.glBindVertexArray(0);
|
||||
GLSMVertexInput.glDeleteVertexArrays(vao);
|
||||
GLSMPrograms.glDeleteProgram(program);
|
||||
}
|
||||
|
||||
private int loc(String name) {
|
||||
return GLSMPrograms.glGetUniformLocation(program, name);
|
||||
}
|
||||
|
||||
private static int argb(int[] rgb) {
|
||||
return 0xFF000000 | (rgb[0] << 16) | (rgb[1] << 8) | rgb[2];
|
||||
}
|
||||
|
||||
private static float[] vec(int[] rgb) {
|
||||
return new float[] {rgb[0] / 255f, rgb[1] / 255f, rgb[2] / 255f, 1f};
|
||||
}
|
||||
|
||||
private static FloatBuffer pack(int[]... colors) {
|
||||
final FloatBuffer buf = MemoryUtil.memAllocFloat(colors.length * 4);
|
||||
for (int[] c : colors) buf.put(vec(c));
|
||||
buf.flip();
|
||||
return buf;
|
||||
}
|
||||
|
||||
private void writeAt(int location, int[]... colors) {
|
||||
final FloatBuffer buf = pack(colors);
|
||||
try {
|
||||
GLSMPrograms.glUniform4(location, buf);
|
||||
} finally {
|
||||
MemoryUtil.memFree(buf);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeElement(int location, int[] rgb) {
|
||||
final float[] v = vec(rgb);
|
||||
GLSMPrograms.glUniform4f(location, v[0], v[1], v[2], v[3]);
|
||||
}
|
||||
|
||||
private void writeAll() {
|
||||
writeAt(arr, A);
|
||||
}
|
||||
|
||||
private int drawPick(int index) {
|
||||
GlsmSdlHeadlessRig.beginFrame();
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GLSMPrograms.glUniform1i(pick, index);
|
||||
GLSMDraws.glDrawArrays(GL11.GL_TRIANGLES, 0, 3);
|
||||
return GlsmSdlHeadlessRig.readTarget(GlsmSdlHeadlessRig.SIZE / 2, GlsmSdlHeadlessRig.SIZE / 2, 1, 1)[0];
|
||||
}
|
||||
|
||||
private void assertPick(int index, int[] expected, String label) {
|
||||
final int got = drawPick(index);
|
||||
assertEquals(Integer.toHexString(argb(expected)), Integer.toHexString(got), label + ": " + GlsmSdlHeadlessRig.describe(got));
|
||||
}
|
||||
|
||||
private float[] readUniform(int location) {
|
||||
final FloatBuffer buf = MemoryUtil.memAllocFloat(4);
|
||||
try {
|
||||
GLSMPrograms.glGetUniform(program, location, buf);
|
||||
final float[] out = new float[4];
|
||||
buf.get(out);
|
||||
return out;
|
||||
} finally {
|
||||
MemoryUtil.memFree(buf);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void arrayElementLocations() {
|
||||
assertNotEquals(-1, arr, "arr");
|
||||
assertNotEquals(-1, single, "single");
|
||||
assertEquals(arr, loc("arr[0]"), "arr[0]");
|
||||
assertEquals(arr + 3, loc("arr[3]"), "arr[3]");
|
||||
assertEquals(arr + 7, loc("arr[7]"), "arr[7]");
|
||||
assertEquals(-1, loc("arr[8]"), "arr[8]");
|
||||
assertEquals(-1, loc("arr[03]"), "arr[03]");
|
||||
assertEquals(-1, loc("arr[-1]"), "arr[-1]");
|
||||
assertEquals(arr, loc("arr[]"), "arr[] is index 0, as Mesa parses it");
|
||||
assertEquals(single, loc("single[0]"), "single[0]");
|
||||
assertEquals(-1, loc("single[1]"), "single[1]");
|
||||
}
|
||||
|
||||
@Test
|
||||
void elementWriteLandsInTheArray() {
|
||||
writeAll();
|
||||
final int element3 = loc("arr[3]");
|
||||
writeElement(element3, X);
|
||||
|
||||
assertPick(3, X, "pick=3 shows the element write");
|
||||
assertPick(2, A[2], "pick=2 keeps the bulk write");
|
||||
assertArrayEquals(vec(X), readUniform(element3), 0f, "glGetUniform arr[3]");
|
||||
assertArrayEquals(vec(A[2]), readUniform(loc("arr[2]")), 0f, "glGetUniform arr[2]");
|
||||
}
|
||||
|
||||
@Test
|
||||
void elementWriteClampsToTheArrayEnd() {
|
||||
writeAll();
|
||||
writeAt(loc("arr[6]"), B);
|
||||
|
||||
assertPick(6, B[0], "pick=6");
|
||||
assertPick(7, B[1], "pick=7");
|
||||
assertPick(5, A[5], "pick=5");
|
||||
}
|
||||
|
||||
@Test
|
||||
void countBeyondTheDeclarationFollowsMesa() {
|
||||
writeElement(single, A[0]);
|
||||
writeAt(single, B);
|
||||
|
||||
assertPick(-1, A[0], "count 2 on a non-array is ignored");
|
||||
assertArrayEquals(vec(A[0]), readUniform(single), 0f, "glGetUniform single keeps its value");
|
||||
|
||||
writeAt(loc("one"), B);
|
||||
assertPick(-3, B[0], "count 2 on a size-1 array clamps");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fullWriteAfterElementWriteIsNotElided() {
|
||||
writeAll();
|
||||
writeElement(loc("arr[3]"), X);
|
||||
writeAll();
|
||||
|
||||
assertPick(3, A[3], "pick=3 after the repeated full write");
|
||||
}
|
||||
|
||||
@Test
|
||||
void intElementReadsBackAsInts() {
|
||||
final int element1 = loc("iarr[1]");
|
||||
assertEquals(loc("iarr") + 1, element1, "iarr[1]");
|
||||
GLSMPrograms.glUniform4i(loc("iarr"), 1, 2, 3, 4);
|
||||
GLSMPrograms.glUniform4i(element1, X[0], X[1], X[2], 255);
|
||||
|
||||
assertPick(-2, X, "pick=-2 shows iarr[1]");
|
||||
assertArrayEquals(new int[] {X[0], X[1], X[2], 255}, readIntUniform(element1), "glGetUniformiv iarr[1]");
|
||||
assertArrayEquals(new int[] {1, 2, 3, 4}, readIntUniform(loc("iarr")), "glGetUniformiv iarr[0]");
|
||||
}
|
||||
|
||||
private int[] readIntUniform(int location) {
|
||||
final IntBuffer buf = MemoryUtil.memAllocInt(4);
|
||||
try {
|
||||
GLSMPrograms.glGetUniform(program, location, buf);
|
||||
final int[] out = new int[4];
|
||||
buf.get(out);
|
||||
return out;
|
||||
} finally {
|
||||
MemoryUtil.memFree(buf);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void activeUniformReportsArrayNameWithIndexZero() {
|
||||
final Map<String, Integer> sizes = activeUniformSizes();
|
||||
assertEquals(COUNT, sizes.getOrDefault("arr[0]", -1), "size of arr[0] in " + sizes);
|
||||
assertEquals(1, sizes.getOrDefault("single", -1), "size of single in " + sizes);
|
||||
assertEquals(1, sizes.getOrDefault("one[0]", -1), "size of one[0] in " + sizes);
|
||||
}
|
||||
|
||||
private Map<String, Integer> activeUniformSizes() {
|
||||
final int active = GLSMPrograms.glGetProgrami(program, GL20.GL_ACTIVE_UNIFORMS);
|
||||
final Map<String, Integer> sizes = new HashMap<>();
|
||||
final IntBuffer length = MemoryUtil.memAllocInt(1);
|
||||
final IntBuffer size = MemoryUtil.memAllocInt(1);
|
||||
final IntBuffer type = MemoryUtil.memAllocInt(1);
|
||||
final ByteBuffer name = MemoryUtil.memAlloc(128);
|
||||
try {
|
||||
for (int i = 0; i < active; i++) {
|
||||
GLSMPrograms.glGetActiveUniform(program, i, length, size, type, name);
|
||||
final byte[] bytes = new byte[length.get(0)];
|
||||
for (int c = 0; c < bytes.length; c++) bytes[c] = name.get(c);
|
||||
sizes.put(new String(bytes, java.nio.charset.StandardCharsets.US_ASCII), size.get(0));
|
||||
}
|
||||
} finally {
|
||||
MemoryUtil.memFree(name);
|
||||
MemoryUtil.memFree(type);
|
||||
MemoryUtil.memFree(size);
|
||||
MemoryUtil.memFree(length);
|
||||
}
|
||||
return sizes;
|
||||
}
|
||||
}
|
||||
+15
-12
@@ -1,12 +1,10 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.resource;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SdlAsserts;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
@@ -19,7 +17,7 @@ class ReadbackShadowsTest {
|
||||
private static final int SLOT_STRIDE = 64;
|
||||
|
||||
private static ReadbackShadows shadows() {
|
||||
return new ReadbackShadows(null);
|
||||
return new ReadbackShadows(null, null, null);
|
||||
}
|
||||
|
||||
private static ByteBuffer out(int bytes) {
|
||||
@@ -36,15 +34,24 @@ class ReadbackShadowsTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidateTouchesOnlyItsOwnSlot() {
|
||||
void invalidateAllTouchesOnlyItsBuffer() {
|
||||
final ReadbackShadows shadows = shadows();
|
||||
SdlReflect.putReadbackSlot(shadows, 7, 0, SENTINEL_HANDLE, 64, true);
|
||||
SdlReflect.putReadbackSlot(shadows, 7, 64, SENTINEL_HANDLE, 64, true);
|
||||
SdlReflect.putReadbackSlot(shadows, 8, 0, SENTINEL_HANDLE, 64, true);
|
||||
|
||||
shadows.invalidate(7, 0);
|
||||
shadows.invalidateAll(7);
|
||||
|
||||
assertFalse(shadows.serve(7, 0, out(64), 64), "the invalidated slot is still being served");
|
||||
assertTrue(SdlReflect.readbackSlotValid(shadows, 7, 64), "invalidating one ring slot must not disturb the others");
|
||||
assertFalse(shadows.serve(7, 0, out(64), 64), "an invalidated slot is still being served");
|
||||
assertFalse(SdlReflect.readbackSlotValid(shadows, 7, 64), "every slot of the written buffer must be invalidated");
|
||||
assertTrue(SdlReflect.readbackSlotValid(shadows, 8, 0), "a write to one buffer must not invalidate another's slots");
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidateAllOfAnUnshadowedBufferDoesNotAllocate() {
|
||||
final ReadbackShadows shadows = shadows();
|
||||
SdlReflect.putReadbackSlot(shadows, 8, 0, SENTINEL_HANDLE, 64, true);
|
||||
SdlAsserts.assertAllocationFree(() -> shadows.invalidateAll(7), "buffer writes while another buffer is shadowed");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -60,10 +67,6 @@ class ReadbackShadowsTest {
|
||||
assertTrue(shadows.hasSlotsFor(8), "deleting one buffer must not drop another's slots");
|
||||
}
|
||||
|
||||
private static int slotCount(ReadbackShadows shadows) {
|
||||
return Reflect.<Map<Long, Object>>get(shadows, "slots").size();
|
||||
}
|
||||
|
||||
private static ReadbackShadows ringShadows(int... glIds) {
|
||||
final ReadbackShadows shadows = shadows();
|
||||
for (int glId : glIds) {
|
||||
@@ -81,7 +84,7 @@ class ReadbackShadowsTest {
|
||||
shadows.release(2);
|
||||
|
||||
assertFalse(shadows.hasSlotsFor(2));
|
||||
assertEquals(2 * SLOTS_PER_BUFFER, slotCount(shadows));
|
||||
assertEquals(2 * SLOTS_PER_BUFFER, SdlReflect.readbackSlotCount(shadows));
|
||||
for (int glId = 1; glId <= 3; glId += 2) {
|
||||
for (int i = 0; i < SLOTS_PER_BUFFER; i++) {
|
||||
assertTrue(SdlReflect.readbackSlotValid(shadows, glId, (long) i * SLOT_STRIDE));
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.resource;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
|
||||
public final class SdlReflect {
|
||||
@@ -30,20 +31,27 @@ public final class SdlReflect {
|
||||
final Object slot = Reflect.construct(slotClass);
|
||||
Reflect.setDeclared(slotClass, slot, "transferBuffer", handle);
|
||||
Reflect.setDeclared(slotClass, slot, "capacity", capacity);
|
||||
Reflect.setDeclared(slotClass, slot, "validLen", capacity);
|
||||
Reflect.setDeclared(slotClass, slot, "valid", valid);
|
||||
readbackSlots(shadows).put(readbackKey(glId, offset), slot);
|
||||
readbackSlots(shadows).computeIfAbsent(glId, k -> new Long2ObjectOpenHashMap<>()).put(offset, slot);
|
||||
}
|
||||
|
||||
static boolean readbackSlotValid(ReadbackShadows shadows, int glId, long offset) {
|
||||
return Reflect.get(readbackSlots(shadows).get(readbackKey(glId, offset)), "valid");
|
||||
return Reflect.get(readbackSlots(shadows).get(glId).get(offset), "valid");
|
||||
}
|
||||
|
||||
private static long readbackKey(int glId, long offset) {
|
||||
return (((long) glId) << 32) | (offset & 0xFFFFFFFFL);
|
||||
public static long readbackSlotSync(ReadbackShadows shadows, int glId, long offset) {
|
||||
return Reflect.get(readbackSlots(shadows).get(glId).get(offset), "sync");
|
||||
}
|
||||
|
||||
private static Map<Long, Object> readbackSlots(ReadbackShadows shadows) {
|
||||
return Reflect.get(shadows, "slots");
|
||||
static int readbackSlotCount(ReadbackShadows shadows) {
|
||||
int count = 0;
|
||||
for (Long2ObjectOpenHashMap<Object> slots : readbackSlots(shadows).values()) count += slots.size();
|
||||
return count;
|
||||
}
|
||||
|
||||
private static Int2ObjectOpenHashMap<Long2ObjectOpenHashMap<Object>> readbackSlots(ReadbackShadows shadows) {
|
||||
return Reflect.get(shadows, "byBuffer");
|
||||
}
|
||||
|
||||
static void recordBuffer(ResourceManager rm, int glId, long handle, long size, int usage) {
|
||||
|
||||
@@ -28,6 +28,7 @@ import com.gtnewhorizons.angelica.glsm.streaming.StreamingUploader;
|
||||
import com.gtnewhorizons.angelica.hudcaching.HUDCaching;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.BatchDrawDefaults;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.EntityShadowReplay;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.FlattenedItemLighting;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.ModelPartBatcher;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.TesrBatchRenderer;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
@@ -1194,6 +1195,7 @@ public class BatchingFontRenderer {
|
||||
final Tessellator tessellator = Tessellator.instance;
|
||||
int lastTexture = INVALID_TEXTURE;
|
||||
boolean drawing = false;
|
||||
FlattenedItemLighting.setPreLitText(true);
|
||||
try {
|
||||
for (int i = from; i < to; i++) {
|
||||
final FontDrawCmd cmd = cmdsData[i];
|
||||
@@ -1219,8 +1221,12 @@ public class BatchingFontRenderer {
|
||||
owner.emitCommand(tessellator, cmd);
|
||||
}
|
||||
} finally {
|
||||
if (drawing) {
|
||||
tessellator.draw();
|
||||
try {
|
||||
if (drawing) {
|
||||
tessellator.draw();
|
||||
}
|
||||
} finally {
|
||||
FlattenedItemLighting.setPreLitText(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,6 +99,22 @@ public class TracyOptionPages {
|
||||
.build())
|
||||
.add(new ActionOption(I18n.format("options.angelica.tracy.captureAction"), I18n.format("options.angelica.tracy.captureAction.tooltip"),
|
||||
captureActionLabel, TracyCaptureNotifier.INSTANCE::toggle, () -> Tracy.ENABLED))
|
||||
.build(),
|
||||
OptionGroup.createBuilder()
|
||||
.add(OptionImpl.createBuilder(boolean.class, angelicaOpts)
|
||||
.setName(I18n.format("options.angelica.tracy.frameImages"))
|
||||
.setTooltip(overrideTooltip("options.angelica.tracy.frameImages.tooltip", "angelica.tracy.frameImages", SystemProperties.TRACY_FRAME_IMAGES_OVERRIDE != null))
|
||||
.setControl(TickBoxControl::new)
|
||||
.setBinding((opts, value) -> AngelicaConfig.tracyFrameImages = value, opts -> AngelicaConfig.tracyFrameImages)
|
||||
.setEnabled(Tracy.ENABLED && SystemProperties.TRACY_FRAME_IMAGES_OVERRIDE == null)
|
||||
.build())
|
||||
.add(OptionImpl.createBuilder(int.class, angelicaOpts)
|
||||
.setName(I18n.format("options.angelica.tracy.frameImageRate"))
|
||||
.setTooltip(overrideTooltip("options.angelica.tracy.frameImageRate.tooltip", "angelica.tracy.frameImageRate", SystemProperties.TRACY_FRAME_IMAGE_RATE_OVERRIDE != null))
|
||||
.setControl(o -> new SliderControl(o, 0, 60, 5, ControlValueFormatter.quantityOrDisabled("options.angelica.tracy.frameImageRate.value", "options.angelica.tracy.frameImageRate.everyFrame")))
|
||||
.setBinding((opts, value) -> AngelicaConfig.tracyFrameImageRate = value, opts -> AngelicaConfig.tracyFrameImageRate)
|
||||
.setEnabled(Tracy.ENABLED && SystemProperties.TRACY_FRAME_IMAGE_RATE_OVERRIDE == null)
|
||||
.build())
|
||||
.build()));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.gtnewhorizons.angelica.common;
|
||||
|
||||
import net.minecraft.block.BlockContainer;
|
||||
import net.minecraft.block.material.Material;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class BlockClipPlaneTest extends BlockContainer {
|
||||
|
||||
public BlockClipPlaneTest() {
|
||||
super(Material.iron);
|
||||
setHardness(0.7f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TileEntity createNewTileEntity(World world, int meta) {
|
||||
return new TileEntityClipPlaneTest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpaqueCube() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean renderAsNormalBlock() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRenderType() {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.gtnewhorizons.angelica.common;
|
||||
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
|
||||
public class TileEntityClipPlaneTest extends TileEntity {
|
||||
|
||||
@Override
|
||||
public boolean canUpdate() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import com.gtnewhorizons.angelica.glsm.GLSMVertexInput;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.StateSet;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.FragmentKey;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.BatchStateGuard;
|
||||
import com.gtnewhorizons.angelica.glsm.states.FogState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
|
||||
import net.coderbot.iris.gl.framebuffer.GlFramebuffer;
|
||||
@@ -181,6 +182,7 @@ final class PortalDrawer {
|
||||
}
|
||||
|
||||
void drawDirect(float[] faces, int count, int tunnelTexture, int fieldTexture, Frame frame, boolean fogged) {
|
||||
BatchStateGuard.beforeChange();
|
||||
final int fogMode = fogged ? FragmentKey.activeFogMode(GLStateManager.ctx()) : FragmentKey.FOG_NONE;
|
||||
final GlProgram<Uniforms> program;
|
||||
if (fogMode == FragmentKey.FOG_NONE) {
|
||||
|
||||
@@ -556,6 +556,15 @@ public class AngelicaConfig {
|
||||
@Config.RangeInt(min = 0, max = 3600)
|
||||
public static int tracyCaptureSeconds;
|
||||
|
||||
@Config.Comment("If enabled, attaches downscaled screenshots to Tracy frames. Overridden by -Dangelica.tracy.frameImages")
|
||||
@Config.DefaultBoolean(false)
|
||||
public static boolean tracyFrameImages;
|
||||
|
||||
@Config.Comment("Tracy frame images captured per second. 0 = every frame. Overridden by -Dangelica.tracy.frameImageRate")
|
||||
@Config.DefaultInt(10)
|
||||
@Config.RangeInt(min = 0, max = 60)
|
||||
public static int tracyFrameImageRate;
|
||||
|
||||
public static void applyGpuCullingMode() {
|
||||
GpuCulling.setMode(gpuCullingMode == null ? GpuCullingMode.CPU_ONLY : gpuCullingMode);
|
||||
}
|
||||
|
||||
@@ -34,9 +34,6 @@ public final class PositionEntries {
|
||||
}
|
||||
}
|
||||
|
||||
private WorldProvider cachedProvider;
|
||||
private String dimensionName = "";
|
||||
|
||||
@Override
|
||||
public void display(DebugScreenDisplayer displayer, DebugContext context) {
|
||||
final EntityClientPlayerMP player = context.player();
|
||||
@@ -52,10 +49,7 @@ public final class PositionEntries {
|
||||
facing.value(DIRECTION_NAMES[heading]).text(" (").text(TOWARDS[heading]).text(") (");
|
||||
facing.value(MathHelper.wrapAngleTo180_float(player.rotationYaw), 1).text(SEPARATOR).value(MathHelper.wrapAngleTo180_float(player.rotationPitch), 1).text(')');
|
||||
final WorldProvider provider = context.world().provider;
|
||||
if (provider != cachedProvider) {
|
||||
cachedProvider = provider;
|
||||
dimensionName = provider.getDimensionName();
|
||||
}
|
||||
final String dimensionName = provider.getDimensionName();
|
||||
displayer.fact(DebugGroup.POSITION, "Dimension").value(dimensionName).text(" (").value(provider.dimensionId).text(')');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,6 +274,13 @@ public class IrisGLSMBridge {
|
||||
refreshBlendCondition();
|
||||
});
|
||||
|
||||
GLSMHooks.CLIP_PLANES_IN_USE.addListener(event -> {
|
||||
if (!Iris.enabled) return;
|
||||
if (Iris.getPipelineManager().getPipelineNullable() instanceof DeferredWorldRenderingPipeline drp) {
|
||||
drp.onClipPlanesInUseChanged();
|
||||
}
|
||||
});
|
||||
|
||||
GLSMHooks.FOREIGN_DRAW_END.addListener(event -> {
|
||||
if (!Iris.enabled) return;
|
||||
if (programRestoreDeferred) {
|
||||
@@ -336,12 +343,13 @@ public class IrisGLSMBridge {
|
||||
WorldRenderingPipeline pipeline = Iris.getPipelineManager().getPipelineNullable();
|
||||
if (pipeline instanceof DeferredWorldRenderingPipeline drp) {
|
||||
DeferredWorldRenderingPipeline.Pass activePass = drp.getActivePassProgram();
|
||||
if (activePass != null && activePass.getProgram() != null && activePass.getProgram().getProgramId() == event.newProgram) {
|
||||
final Program bound = activePass != null ? activePass.boundProgram() : null;
|
||||
if (bound != null && bound.getProgramId() == event.newProgram) {
|
||||
final int frame = SystemTimeUniforms.COUNTER.getAsInt();
|
||||
if (programLastUpdatedFrame.get(event.newProgram) != frame
|
||||
|| !ProgramUniforms.isActiveProgramBound()
|
||||
|| ProgramUniforms.activeHasDeferredUploads()) {
|
||||
activePass.getProgram().getUniforms().update();
|
||||
bound.getUniforms().update();
|
||||
programLastUpdatedFrame.put(event.newProgram, frame);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
package com.gtnewhorizons.angelica.profiling;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import com.gtnewhorizons.angelica.config.SystemProperties;
|
||||
import com.gtnewhorizons.angelica.glsm.CaptureGate;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMFramebuffer;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.ScreenCapture;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.shader.Framebuffer;
|
||||
|
||||
public final class TracyFrameImages {
|
||||
private static final int MAX_WIDTH = 320;
|
||||
private static final int MAX_HEIGHT = 180;
|
||||
private static final Tracy.ZoneId Z_CAPTURE = Tracy.zoneId("frameImageCapture", Tracy.COLOR_SWAP);
|
||||
private static final Tracy.ZoneId Z_EMIT = Tracy.zoneId("frameImageEmit", Tracy.COLOR_SWAP);
|
||||
private static final boolean HAS_ENABLED_OVERRIDE = SystemProperties.TRACY_FRAME_IMAGES_OVERRIDE != null;
|
||||
private static final boolean ENABLED_OVERRIDE = HAS_ENABLED_OVERRIDE && SystemProperties.TRACY_FRAME_IMAGES_OVERRIDE;
|
||||
private static final boolean HAS_RATE_OVERRIDE = SystemProperties.TRACY_FRAME_IMAGE_RATE_OVERRIDE != null;
|
||||
private static final int RATE_OVERRIDE = HAS_RATE_OVERRIDE ? Math.max(0, SystemProperties.TRACY_FRAME_IMAGE_RATE_OVERRIDE) : 0;
|
||||
|
||||
private static ScreenCapture capture;
|
||||
private static long frameIndex;
|
||||
private static long lastCaptureNs;
|
||||
private static int srcW;
|
||||
private static int srcH;
|
||||
|
||||
private TracyFrameImages() {}
|
||||
|
||||
public static void onPostSwap(Minecraft mc) {
|
||||
if (!enabled() || !CaptureGate.tracyConnected) {
|
||||
drop();
|
||||
return;
|
||||
}
|
||||
emitReady();
|
||||
final long now = System.nanoTime();
|
||||
final int rate = rate();
|
||||
if (rate != 0 && now - lastCaptureNs < 1_000_000_000L / rate) return;
|
||||
final Framebuffer source = mc.getFramebuffer();
|
||||
final int w = source.framebufferWidth;
|
||||
final int h = source.framebufferHeight;
|
||||
if (w <= 0 || h <= 0) return;
|
||||
if (capture == null || w != srcW || h != srcH) {
|
||||
srcW = w;
|
||||
srcH = h;
|
||||
final long size = fitCaptureSize(w, h);
|
||||
final int captureW = (int) (size >>> 32);
|
||||
final int captureH = (int) size;
|
||||
if (capture == null || capture.width() != captureW || capture.height() != captureH) {
|
||||
if (capture != null) capture.delete();
|
||||
capture = GLSMFramebuffer.createScreenCapture(captureW, captureH);
|
||||
}
|
||||
return;
|
||||
}
|
||||
Tracy.beginZone(Z_CAPTURE);
|
||||
if (capture.begin(source.framebufferObject, w, h, frameIndex)) lastCaptureNs = now;
|
||||
Tracy.endZone();
|
||||
}
|
||||
|
||||
static long fitCaptureSize(int srcWidth, int srcHeight) {
|
||||
final float aspect = (float) srcWidth / srcHeight;
|
||||
int width = srcWidth;
|
||||
int height = srcHeight;
|
||||
if (width > MAX_WIDTH) {
|
||||
width = MAX_WIDTH;
|
||||
height = (int) (MAX_WIDTH / aspect);
|
||||
}
|
||||
if (height > MAX_HEIGHT) {
|
||||
width = (int) (MAX_HEIGHT * aspect);
|
||||
height = MAX_HEIGHT;
|
||||
}
|
||||
return ((long) Math.max(4, width & ~3) << 32) | Math.max(4, height & ~3);
|
||||
}
|
||||
|
||||
public static void onFrameMark() {
|
||||
frameIndex++;
|
||||
}
|
||||
|
||||
private static void emitReady() {
|
||||
if (capture == null || capture.idle()) return;
|
||||
Tracy.beginZone(Z_EMIT);
|
||||
while (capture.poll()) {
|
||||
final long lag = frameIndex - capture.tag();
|
||||
if (lag <= 255) Tracy.frameImage(capture.address(), capture.width(), capture.height(), (int) lag, true);
|
||||
capture.release();
|
||||
}
|
||||
Tracy.endZone();
|
||||
}
|
||||
|
||||
private static void drop() {
|
||||
if (capture == null) return;
|
||||
capture.delete();
|
||||
capture = null;
|
||||
srcW = srcH = 0;
|
||||
}
|
||||
|
||||
private static boolean enabled() {
|
||||
return HAS_ENABLED_OVERRIDE ? ENABLED_OVERRIDE : AngelicaConfig.tracyFrameImages;
|
||||
}
|
||||
|
||||
private static int rate() {
|
||||
return HAS_RATE_OVERRIDE ? RATE_OVERRIDE : AngelicaConfig.tracyFrameImageRate;
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,7 @@ import com.gtnewhorizons.angelica.commands.AngelicaCommand;
|
||||
import com.gtnewhorizons.angelica.AngelicaMod;
|
||||
import com.gtnewhorizons.angelica.common.BlockError;
|
||||
import com.gtnewhorizons.angelica.common.BlockIsbrhTest;
|
||||
import com.gtnewhorizons.angelica.common.TileEntityClipPlaneTest;
|
||||
import com.gtnewhorizons.angelica.compat.ModStatus;
|
||||
import com.gtnewhorizons.angelica.compat.bettercrashes.BetterCrashesCompat;
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
@@ -70,6 +71,7 @@ import com.gtnewhorizons.angelica.mixins.interfaces.IGameSettingsExt;
|
||||
import com.gtnewhorizons.angelica.render.CloudRenderer;
|
||||
import com.gtnewhorizons.angelica.render.EmissiveTextureAutoloader;
|
||||
import com.gtnewhorizons.angelica.rendering.AngelicaBlockSafetyRegistry;
|
||||
import com.gtnewhorizons.angelica.rendering.ClipPlaneTestRenderer;
|
||||
import com.gtnewhorizons.angelica.rendering.FpsReducer;
|
||||
import com.gtnewhorizons.angelica.rendering.FramePacer;
|
||||
import com.gtnewhorizons.angelica.rendering.IsbrhTestRenderer;
|
||||
@@ -86,6 +88,7 @@ import com.gtnewhorizons.angelica.utils.AngelicaJar;
|
||||
import com.gtnewhorizons.angelica.utils.AnimationMode;
|
||||
import com.gtnewhorizons.angelica.utils.ManagedEnum;
|
||||
import com.gtnewhorizons.angelica.zoom.Zoom;
|
||||
import cpw.mods.fml.client.registry.ClientRegistry;
|
||||
import cpw.mods.fml.client.registry.RenderingRegistry;
|
||||
import cpw.mods.fml.common.FMLCommonHandler;
|
||||
import cpw.mods.fml.common.event.FMLInitializationEvent;
|
||||
@@ -146,6 +149,7 @@ public final class ClientProxy extends CommonProxy {
|
||||
if (AngelicaConfig.enableTestBlocks) {
|
||||
BlockIsbrhTest.renderId = RenderingRegistry.getNextAvailableRenderId();
|
||||
RenderingRegistry.registerBlockHandler(BlockIsbrhTest.renderId, new IsbrhTestRenderer());
|
||||
ClientRegistry.bindTileEntitySpecialRenderer(TileEntityClipPlaneTest.class, new ClipPlaneTestRenderer());
|
||||
}
|
||||
AngelicaJar.configureShaderDiskCache(event.getSourceFile());
|
||||
if (AngelicaConfig.enableIris) {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.gtnewhorizons.angelica.proxy;
|
||||
|
||||
import com.gtnewhorizons.angelica.common.BlockClipPlaneTest;
|
||||
import com.gtnewhorizons.angelica.common.BlockIsbrhTest;
|
||||
import com.gtnewhorizons.angelica.common.BlockTest;
|
||||
import com.gtnewhorizons.angelica.common.TileEntityClipPlaneTest;
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import cpw.mods.fml.common.event.FMLInitializationEvent;
|
||||
import cpw.mods.fml.common.event.FMLPostInitializationEvent;
|
||||
@@ -14,6 +16,8 @@ public class CommonProxy {
|
||||
if (AngelicaConfig.enableTestBlocks) {
|
||||
GameRegistry.registerBlock(new BlockTest(), "test_block");
|
||||
GameRegistry.registerBlock(new BlockIsbrhTest(), "test_isbrh");
|
||||
GameRegistry.registerBlock(new BlockClipPlaneTest(), "test_clip_plane");
|
||||
GameRegistry.registerTileEntity(TileEntityClipPlaneTest.class, "angelica:test_clip_plane");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.gtnewhorizons.angelica.rendering;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMEnable;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMMatrices;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMRasterizer;
|
||||
import net.minecraft.client.renderer.RenderBlocks;
|
||||
import net.minecraft.client.renderer.texture.TextureMap;
|
||||
import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer;
|
||||
import net.minecraft.init.Blocks;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.nio.DoubleBuffer;
|
||||
|
||||
public class ClipPlaneTestRenderer extends TileEntitySpecialRenderer {
|
||||
|
||||
private static final DoubleBuffer KEEP_LOWER_HALF = plane(0.0, -1.0, 0.0, 0.5);
|
||||
private static final DoubleBuffer REJECT_ALL = plane(0.0, 0.0, 0.0, -1.0);
|
||||
|
||||
private static DoubleBuffer plane(double a, double b, double c, double d) {
|
||||
final DoubleBuffer buf = BufferUtils.createDoubleBuffer(4);
|
||||
buf.put(a).put(b).put(c).put(d).flip();
|
||||
return buf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderTileEntityAt(TileEntity te, double x, double y, double z, float partialTicks) {
|
||||
GLSMMatrices.glPushMatrix();
|
||||
GLSMMatrices.glTranslated(x, y, z);
|
||||
GLSMRasterizer.glClipPlane(GL11.GL_CLIP_PLANE1, REJECT_ALL);
|
||||
GLSMEnable.glDisable(GL11.GL_CLIP_PLANE1);
|
||||
GLSMRasterizer.glClipPlane(GL11.GL_CLIP_PLANE0, KEEP_LOWER_HALF);
|
||||
GLSMEnable.glEnable(GL11.GL_CLIP_PLANE0);
|
||||
GLSMMatrices.glTranslated(0.5, 0.5, 0.5);
|
||||
bindTexture(TextureMap.locationBlocksTexture);
|
||||
RenderBlocks.getInstance().renderBlockAsItem(Blocks.gold_block, 0, 1.0f);
|
||||
GLSMEnable.glDisable(GL11.GL_CLIP_PLANE0);
|
||||
GLSMMatrices.glPopMatrix();
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import com.gtnewhorizons.angelica.rendering.tesr.EntityMaterials;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.GlintCapture;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.ModelPartBatcher;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.EntityShadowReplay;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.FlattenedItemLighting;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.TemplateBuffer;
|
||||
import com.gtnewhorizons.angelica.utils.AnimationsRenderUtils;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
@@ -26,6 +27,8 @@ import net.minecraft.client.renderer.RenderBlocks;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.client.renderer.cel.util.ModelQuadUtil.VERTEX_SIZE;
|
||||
|
||||
public final class DroppedItemInstancer {
|
||||
|
||||
public static final long SKIP = -2L;
|
||||
@@ -93,7 +96,8 @@ public final class DroppedItemInstancer {
|
||||
|
||||
private static boolean glintEligible(ItemStack stack) {
|
||||
return stack != null && stack.getItem() != null && ModelPartBatcher.INSTANCE.isActive() && !GLStateManager.isRecordingDisplayList()
|
||||
&& !TessellatorManager.isCurrentlyCapturing() && !TessellatorManager.shouldInterceptDraw(Tessellator.instance);
|
||||
&& !TessellatorManager.isCurrentlyCapturing() && !TessellatorManager.shouldInterceptDraw(Tessellator.instance)
|
||||
&& !FlattenedItemLighting.applies();
|
||||
}
|
||||
|
||||
static TesrMaterial material(ItemStack stack, boolean unfilteredAtlas) {
|
||||
@@ -219,6 +223,29 @@ public final class DroppedItemInstancer {
|
||||
return material != null ? batchBlock(material, rb, block, meta, brightness) : GLSMDraws.drawCalls;
|
||||
}
|
||||
|
||||
public static boolean drawFlattenedBlock(RenderBlocks rb, Block block, int meta, float brightness) {
|
||||
final BlockMesh mesh = blockMesh(rb, block, meta, brightness);
|
||||
final TemplateBuffer template = mesh.template();
|
||||
if (template == null) return false;
|
||||
mesh.sprites().markUsed();
|
||||
final Tessellator tess = Tessellator.instance;
|
||||
final int ints = template.vertexCount * VERTEX_SIZE;
|
||||
tess.startDrawing(template.drawMode);
|
||||
if (tess.rawBuffer == null || tess.rawBufferSize < ints) {
|
||||
tess.rawBufferSize = Math.max(0x10000, ints);
|
||||
tess.rawBuffer = new int[tess.rawBufferSize];
|
||||
}
|
||||
System.arraycopy(template.data, 0, tess.rawBuffer, 0, ints);
|
||||
tess.vertexCount = template.vertexCount;
|
||||
tess.rawBufferIndex = ints;
|
||||
tess.hasTexture = true;
|
||||
tess.hasNormals = true;
|
||||
tess.hasColor = false;
|
||||
tess.hasBrightness = false;
|
||||
tess.draw();
|
||||
return true;
|
||||
}
|
||||
|
||||
private static long itemBlock(ItemStack stack, RenderBlocks rb, Block block, int meta, float brightness, boolean unfilteredAtlas) {
|
||||
final TesrMaterial material = material(stack, unfilteredAtlas);
|
||||
if (material == null) {
|
||||
@@ -236,12 +263,7 @@ public final class DroppedItemInstancer {
|
||||
static long batchBlock(TesrMaterial material, RenderBlocks rb, Block block, int meta, float brightness) {
|
||||
final long drawsBefore = GLSMDraws.drawCalls;
|
||||
if (BatchEligibility.batchingAllowed()) {
|
||||
BlockMesh mesh = lookupBlock(block, meta);
|
||||
if (mesh == null) {
|
||||
mesh = captureBlock(rb, block, meta, brightness);
|
||||
} else if (mesh.template() != null) {
|
||||
GLSMCurrentAttribs.glColor4f(mesh.red(), mesh.green(), mesh.blue(), mesh.alpha());
|
||||
}
|
||||
final BlockMesh mesh = blockMesh(rb, block, meta, brightness);
|
||||
if (mesh.template() == null) {
|
||||
fallback++;
|
||||
bail(BailReason.TEMPLATE);
|
||||
@@ -338,6 +360,13 @@ public final class DroppedItemInstancer {
|
||||
return mesh;
|
||||
}
|
||||
|
||||
private static BlockMesh blockMesh(RenderBlocks rb, Block block, int meta, float brightness) {
|
||||
final BlockMesh hit = lookupBlock(block, meta);
|
||||
if (hit == null) return captureBlock(rb, block, meta, brightness);
|
||||
if (hit.template() != null) GLSMCurrentAttribs.glColor4f(hit.red(), hit.green(), hit.blue(), hit.alpha());
|
||||
return hit;
|
||||
}
|
||||
|
||||
private static BlockMesh lookupBlock(Block block, int meta) {
|
||||
blockKey.block = block;
|
||||
blockKey.meta = meta;
|
||||
|
||||
@@ -34,7 +34,8 @@ public final class BatchDrawDefaults {
|
||||
}
|
||||
|
||||
public void apply() {
|
||||
if (passFramebuffer >= 0 && TesrBatchRenderer.deferredPipeline() == null) {
|
||||
final boolean pack = TesrBatchRenderer.deferredPipeline() != null;
|
||||
if (passFramebuffer >= 0 && !pack) {
|
||||
final int framebuffer = GLSMFramebuffer.getDrawFramebuffer();
|
||||
if (framebuffer != passFramebuffer) {
|
||||
callerFramebuffer = framebuffer;
|
||||
@@ -42,7 +43,7 @@ public final class BatchDrawDefaults {
|
||||
}
|
||||
}
|
||||
final int program = GLSMPrograms.getActiveProgram();
|
||||
if (program != 0 && !Program.isManagedBind(program) && !DepthColorStorage.isOwnedProgram(program)) {
|
||||
if (program != 0 && !Program.isManagedBind(program) && !(pack && DepthColorStorage.isOwnedProgram(program))) {
|
||||
callerProgram = program;
|
||||
GLSMPrograms.glUseProgram(0);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.gtnewhorizons.angelica.rendering.tesr;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMLighting;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMMatrices;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import net.coderbot.iris.shadows.ShadowRenderingState;
|
||||
import org.joml.Matrix4fc;
|
||||
|
||||
public final class FlattenedItemLighting {
|
||||
|
||||
static final float FLATNESS_CUTOFF = 0.05f;
|
||||
|
||||
private static boolean preLitText;
|
||||
|
||||
private FlattenedItemLighting() {}
|
||||
|
||||
public static void setPreLitText(boolean preLit) {
|
||||
preLitText = preLit;
|
||||
}
|
||||
|
||||
static boolean isFlattened(Matrix4fc m) {
|
||||
final float ax = m.m00(), ay = m.m01(), az = m.m02();
|
||||
final float bx = m.m10(), by = m.m11(), bz = m.m12();
|
||||
final float cx = m.m20(), cy = m.m21(), cz = m.m22();
|
||||
final float bcx = by * cz - bz * cy, bcy = bz * cx - bx * cz, bcz = bx * cy - by * cx;
|
||||
final float cax = cy * az - cz * ay, cay = cz * ax - cx * az, caz = cx * ay - cy * ax;
|
||||
final float abx = ay * bz - az * by, aby = az * bx - ax * bz, abz = ax * by - ay * bx;
|
||||
final float det = ax * bcx + ay * bcy + az * bcz;
|
||||
final float frob2 = ax * ax + ay * ay + az * az + bx * bx + by * by + bz * bz + cx * cx + cy * cy + cz * cz;
|
||||
final float adj2 = bcx * bcx + bcy * bcy + bcz * bcz + cax * cax + cay * cay + caz * caz + abx * abx + aby * aby + abz * abz;
|
||||
if (!(adj2 > 0f) || det == 0f) return false;
|
||||
return Math.abs(det) * (float) Math.sqrt(frob2) < FLATNESS_CUTOFF * adj2;
|
||||
}
|
||||
|
||||
public static boolean applies() {
|
||||
return GLStateManager.isMainThread() && !preLitText && TesrBatchRenderer.INSTANCE.inScope() && GLSMLighting.getLightingState().isEnabled() && !GLStateManager.isRecordingDisplayList()
|
||||
&& TesrShaderPrograms.packActive() && !ShadowRenderingState.areShadowsCurrentlyBeingRendered() && isFlattened(GLSMMatrices.getModelViewMatrix());
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.StateSet;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.BatchStateGuard;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.CubeParams;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.FFPVertexLighting;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.InstancedAttribs;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.Instancing;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
|
||||
@@ -850,6 +851,7 @@ public final class ModelPartBatcher {
|
||||
private enum TessellatorRoute { DRAW_NOW, DRAW_NOW_EXPECTED, QUEUE }
|
||||
|
||||
public static int drawTessellator(Tessellator tess) {
|
||||
if (FlattenedItemLighting.applies() && FFPVertexLighting.bakeColors(tess.rawBuffer, tess.vertexCount, tess.hasNormals, tess.hasColor)) tess.hasColor = true;
|
||||
switch (INSTANCE.routeTessellatorDraw(tess)) {
|
||||
case QUEUE -> {
|
||||
tess.isDrawing = false;
|
||||
|
||||
@@ -95,6 +95,10 @@ public final class TesrBatchRenderer {
|
||||
if (scopeDepth++ == 0) currentScope = ++scopeCounter;
|
||||
}
|
||||
|
||||
public boolean inScope() {
|
||||
return scopeDepth != 0;
|
||||
}
|
||||
|
||||
public void endScope() {
|
||||
if (--scopeDepth == 0) currentScope = 0;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import com.gtnewhorizons.angelica.glsm.ffp.Instancing;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.BatchStateGuard;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.PendingProgramSelection;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
|
||||
import com.gtnewhorizons.angelica.rendering.RenderingState;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.BatchEligibility;
|
||||
@@ -141,6 +142,7 @@ import java.util.OptionalInt;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.IntFunction;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@@ -148,6 +150,8 @@ import java.util.function.Supplier;
|
||||
* Encapsulates the compiled shader program objects for the currently loaded shaderpack.
|
||||
*/
|
||||
public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, RenderTargetStateListener, TesrInstancingPipeline, PendingProgramSelection {
|
||||
private static final Tracy.ZoneId Z_CLIP_VARIANT = Tracy.zoneId("irisClipVariant", Tracy.COLOR_IRIS);
|
||||
|
||||
private final RenderTargets renderTargets;
|
||||
|
||||
@Nullable
|
||||
@@ -202,7 +206,7 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
private final EnumMap<Instancing, Map<Pair<String, InputAvailability>, Map<PatchShaderType, String>>> instancedAttributeTransforms = new EnumMap<>(Instancing.class);
|
||||
private final boolean[] supportsInstancing = initialInstancingSupport();
|
||||
private final Map<Pair<String, InputAvailability>, Map<PatchShaderType, String>> wideLineTransforms = new HashMap<>();
|
||||
private final Map<SharedProgramKey, WideLineProgram> wideLinePrograms = new HashMap<>();
|
||||
private final Map<WideLineKey, WideLineProgram> wideLinePrograms = new HashMap<>();
|
||||
private final boolean packWidensLines;
|
||||
private final ParityFlipState parityState = new ParityFlipState(AngelicaConfig.shaderParityFlip);
|
||||
private final Supplier<ImmutableSet<Integer>> flippedGbuffers;
|
||||
@@ -965,7 +969,7 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
|
||||
public int getActivePassProgramId() {
|
||||
if (current == null) return -1;
|
||||
final Program p = current.getProgram();
|
||||
final Program p = current.boundProgram();
|
||||
return p != null ? p.getProgramId() : -1;
|
||||
}
|
||||
|
||||
@@ -1044,6 +1048,29 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
}
|
||||
}
|
||||
|
||||
private boolean clipRebindPending;
|
||||
|
||||
public void onClipPlanesInUseChanged() {
|
||||
if (current == null) return;
|
||||
clipRebindPending = true;
|
||||
GLSMHooks.pendingProgramSelection = this;
|
||||
}
|
||||
|
||||
private void resolveClipRebind() {
|
||||
if (!clipRebindPending) return;
|
||||
if (DisplayListManager.isRecording()) {
|
||||
GLSMHooks.pendingProgramSelection = this;
|
||||
return;
|
||||
}
|
||||
clipRebindPending = false;
|
||||
final Pass pass = current;
|
||||
if (pass == null) return;
|
||||
final Program live = pass.liveProgram;
|
||||
if (live != null && GLSMPrograms.getActiveProgram() == live.getProgramId() && pass.select(activeInstancing, GLSMRasterizer.clipEmulationActive()) != live) {
|
||||
pass.rebindProgram();
|
||||
}
|
||||
}
|
||||
|
||||
private void clearPendingSelection() {
|
||||
if (GLSMHooks.pendingProgramSelection == this) {
|
||||
GLSMHooks.pendingProgramSelection = null;
|
||||
@@ -1053,11 +1080,10 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
private void matchPass() {
|
||||
clearPendingSelection();
|
||||
|
||||
if (!shouldOverrideShaders()) {
|
||||
return;
|
||||
if (shouldOverrideShaders()) {
|
||||
matchPass(getCondition(getPhase()));
|
||||
}
|
||||
|
||||
matchPass(getCondition(getPhase()));
|
||||
resolveClipRebind();
|
||||
}
|
||||
|
||||
private void matchPass(RenderCondition condition) {
|
||||
@@ -1139,7 +1165,7 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
}
|
||||
return;
|
||||
}
|
||||
final Program program = pass.getProgram();
|
||||
final Program program = pass.select(Instancing.NONE, GLSMRasterizer.clipEmulationActive());
|
||||
final GlFramebuffer expected = isBeforeTranslucent ? pass.framebufferBeforeTranslucents : pass.framebufferAfterTranslucents;
|
||||
if (program != null && GLSMPrograms.getActiveProgram() == program.getProgramId() && GLSMFramebuffer.getDrawFramebuffer() == expected.getId()) {
|
||||
return;
|
||||
@@ -1199,7 +1225,7 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
if (shared != null) {
|
||||
final Pass pass = createPassInner(shared.program(), source.getDirectives(), shadow, id);
|
||||
this.customUniforms.mapPassLike(shared.firstPass(), pass);
|
||||
pass.setInstancingKey(source.getName(), availability, shadow);
|
||||
pass.setVariantSource(source, id, availability, shadow);
|
||||
return pass;
|
||||
}
|
||||
|
||||
@@ -1224,7 +1250,7 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
|
||||
final Pass pass = createPassInner(program, source.getDirectives(), shadow, id);
|
||||
this.customUniforms.mapholderToPass(builder, pass);
|
||||
pass.setInstancingKey(source.getName(), availability, shadow);
|
||||
pass.setVariantSource(source, id, availability, shadow);
|
||||
if (shareKey != null) {
|
||||
builtPrograms.put(shareKey, new BuiltProgram(program, pass));
|
||||
}
|
||||
@@ -1432,18 +1458,24 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
private final List<BufferBlendOverride> bufferBlendOverrides;
|
||||
private final boolean shadowViewport;
|
||||
|
||||
private String instancingSourceName;
|
||||
private InputAvailability instancingAvailability;
|
||||
private boolean instancingShadow;
|
||||
private final Program[] instancedVariants = new Program[Instancing.VALUES.length];
|
||||
private final boolean[] instancedVariantAttempted = new boolean[Instancing.VALUES.length];
|
||||
private ProgramSource variantSource;
|
||||
private ProgramId variantId;
|
||||
private InputAvailability variantAvailability;
|
||||
private boolean variantShadow;
|
||||
private final Program[][] variants = new Program[Instancing.VALUES.length][2];
|
||||
private final boolean[][] variantAttempted = new boolean[Instancing.VALUES.length][2];
|
||||
@Nullable
|
||||
private WideLineProgram wideLineProgram;
|
||||
private boolean wideLineProgramAttempted;
|
||||
private Program boundProgram;
|
||||
@Nullable
|
||||
private Program liveProgram;
|
||||
private final WideLineProgram[] wideLineVariants = new WideLineProgram[2];
|
||||
private final boolean[] wideLineProgramAttempted = new boolean[2];
|
||||
|
||||
private Pass(@Nullable Program program, GlFramebuffer framebufferBeforeTranslucents, GlFramebuffer framebufferAfterTranslucents,
|
||||
@Nullable AlphaTestOverride alphaTestOverride, @Nullable BlendModeOverride blendModeOverride, @Nullable List<BufferBlendOverride> bufferBlendOverrides, boolean shadowViewport) {
|
||||
this.program = program;
|
||||
this.variants[Instancing.NONE.ordinal()][0] = program;
|
||||
this.variantAttempted[Instancing.NONE.ordinal()][0] = true;
|
||||
this.framebufferBeforeTranslucents = framebufferBeforeTranslucents;
|
||||
this.framebufferAfterTranslucents = framebufferAfterTranslucents;
|
||||
this.alphaTestOverride = alphaTestOverride;
|
||||
@@ -1457,15 +1489,22 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
}
|
||||
|
||||
void rebindProgram() {
|
||||
if (program != null) program.use();
|
||||
final boolean suspended = GLStateManager.suspendExitCapture();
|
||||
try {
|
||||
DeferredWorldRenderingPipeline.this.customUniforms.push(this);
|
||||
bindSelected();
|
||||
} finally {
|
||||
GLStateManager.resumeExitCapture(suspended);
|
||||
}
|
||||
}
|
||||
|
||||
private void bindSelected() {
|
||||
final Program selected = select(activeInstancing, GLSMRasterizer.clipEmulationActive());
|
||||
liveProgram = selected;
|
||||
if (activeInstancing == Instancing.NONE) boundProgram = selected;
|
||||
if (selected != null) selected.use();
|
||||
DeferredWorldRenderingPipeline.this.customUniforms.push(selected == null || selected == program ? this : selected);
|
||||
}
|
||||
|
||||
private void useNow() {
|
||||
final boolean suspended = GLStateManager.suspendExitCapture();
|
||||
try {
|
||||
@@ -1511,12 +1550,7 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
bufferBlendOverrides.forEach(BufferBlendOverride::apply);
|
||||
}
|
||||
|
||||
if (activeInstancing != Instancing.NONE && program != null) {
|
||||
DeferredWorldRenderingPipeline.this.bindInstancedVariant(activeInstancing);
|
||||
} else {
|
||||
if (program != null) program.use();
|
||||
DeferredWorldRenderingPipeline.this.customUniforms.push(this);
|
||||
}
|
||||
bindSelected();
|
||||
}
|
||||
|
||||
public void stopUsing() {
|
||||
@@ -1541,62 +1575,113 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
return program;
|
||||
}
|
||||
|
||||
void setInstancingKey(String sourceName, InputAvailability availability, boolean shadow) {
|
||||
this.instancingSourceName = sourceName;
|
||||
this.instancingAvailability = availability;
|
||||
this.instancingShadow = shadow;
|
||||
@Nullable
|
||||
public Program boundProgram() {
|
||||
return boundProgram != null ? boundProgram : program;
|
||||
}
|
||||
|
||||
void setVariantSource(ProgramSource source, ProgramId id, InputAvailability availability, boolean shadow) {
|
||||
this.variantSource = source;
|
||||
this.variantId = id;
|
||||
this.variantAvailability = availability;
|
||||
this.variantShadow = shadow;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Program instancedVariant(Instancing kind) {
|
||||
Program select(Instancing kind, boolean clipped) {
|
||||
final int i = kind.ordinal();
|
||||
if (!instancedVariantAttempted[i]) {
|
||||
instancedVariantAttempted[i] = true;
|
||||
if (instancingSourceName != null && supportsInstancing[i]) {
|
||||
instancedVariants[i] = sharedInstancedVariant(instancingSourceName, instancingAvailability, instancingShadow, kind);
|
||||
if (!variantAttempted[i][0]) {
|
||||
variantAttempted[i][0] = true;
|
||||
if (variantSource != null && supportsInstancing[i]) {
|
||||
variants[i][0] = sharedVariant(new InstancedVariantKey(variantSource.getName(), variantAvailability, variantShadow, kind, false),
|
||||
() -> buildInstancedVariant(variantSource.getName(), variantAvailability, variantShadow, kind));
|
||||
}
|
||||
}
|
||||
return instancedVariants[i];
|
||||
final Program unclipped = variants[i][0];
|
||||
if (!clipped || unclipped == null) return unclipped;
|
||||
if (!variantAttempted[i][1]) {
|
||||
variantAttempted[i][1] = true;
|
||||
variants[i][1] = sharedVariant(new InstancedVariantKey(variantSource.getName(), variantAvailability, variantShadow, kind, true),
|
||||
() -> buildClipVariant(variantSource, variantId, variantAvailability, variantShadow, kind));
|
||||
}
|
||||
return variants[i][1] != null ? variants[i][1] : unclipped;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
WideLineProgram wideLineProgram() {
|
||||
if (!wideLineProgramAttempted) {
|
||||
wideLineProgramAttempted = true;
|
||||
if (instancingSourceName != null) {
|
||||
wideLineProgram = sharedWideLineProgram(instancingSourceName, instancingAvailability, instancingShadow);
|
||||
WideLineProgram wideLineProgram(boolean clipped) {
|
||||
final int c = clipped ? 1 : 0;
|
||||
if (!wideLineProgramAttempted[c]) {
|
||||
wideLineProgramAttempted[c] = true;
|
||||
if (variantSource != null && (!clipped || wideLineProgram(false) != null)) {
|
||||
wideLineVariants[c] = sharedWideLineProgram(variantSource, variantAvailability, variantShadow, clipped);
|
||||
}
|
||||
}
|
||||
return wideLineProgram;
|
||||
return wideLineVariants[c] != null || !clipped ? wideLineVariants[c] : wideLineVariants[0];
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
if (this.program != null) {
|
||||
this.program.destroy();
|
||||
}
|
||||
for (int i = 0; i < instancedVariants.length; i++) {
|
||||
if (instancedVariants[i] != null) {
|
||||
instancedVariants[i].destroy();
|
||||
instancedVariants[i] = null;
|
||||
for (int i = 0; i < variants.length; i++) {
|
||||
for (int c = 0; c < 2; c++) {
|
||||
if ((i != Instancing.NONE.ordinal() || c != 0) && variants[i][c] != null) {
|
||||
variants[i][c].destroy();
|
||||
variants[i][c] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private record InstancedVariantKey(String sourceName, InputAvailability availability, boolean shadow, Instancing kind) {}
|
||||
private record InstancedVariantKey(String sourceName, InputAvailability availability, boolean shadow, Instancing kind, boolean clipped) {}
|
||||
|
||||
// Passes sharing a program also share its instanced variants
|
||||
private final Map<InstancedVariantKey, Program> instancedVariantPrograms = new HashMap<>();
|
||||
|
||||
@Nullable
|
||||
private Program sharedInstancedVariant(String sourceName, InputAvailability availability, boolean shadow, Instancing kind) {
|
||||
final InstancedVariantKey key = new InstancedVariantKey(sourceName, availability, shadow, kind);
|
||||
private Program sharedVariant(InstancedVariantKey key, Supplier<Program> build) {
|
||||
if (instancedVariantPrograms.containsKey(key)) return instancedVariantPrograms.get(key);
|
||||
final Program variant = buildInstancedVariant(sourceName, availability, shadow, kind);
|
||||
final Program variant = build.get();
|
||||
instancedVariantPrograms.put(key, variant);
|
||||
return variant;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Program buildClipVariant(ProgramSource source, ProgramId id, InputAvailability availability, boolean shadow, Instancing kind) {
|
||||
final String name = source.getName() + kind.variantSuffix + "_clipped";
|
||||
final boolean glint = GlintScrollInjector.shouldInject(id, source);
|
||||
return buildClipped(source, name, (vertex, fragment) -> {
|
||||
final Map<PatchShaderType, String> transformed = TransformPatcher.patchAttributesInstanced(vertex, null, null, null, fragment, availability, glint, kind, true);
|
||||
return transformed != null ? linkVariant(name, transformed, availability, shadow) : null;
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static <T> T buildClipped(ProgramSource source, String name, BiFunction<String, String, T> build) {
|
||||
if (source.getGeometrySource().isPresent() || source.getTessControlSource().isPresent() || source.getTessEvalSource().isPresent()) {
|
||||
return null;
|
||||
}
|
||||
if (Tracy.ENABLED) Tracy.beginZone(Z_CLIP_VARIANT);
|
||||
try {
|
||||
return build.apply(source.getVertexSource().orElse(null), source.getFragmentSource().orElse(null));
|
||||
} catch (Exception e) {
|
||||
Iris.logger.warn("Clip plane variant failed for {}; clip planes stay unclipped there", name, e);
|
||||
return null;
|
||||
} finally {
|
||||
if (Tracy.ENABLED) Tracy.endZone();
|
||||
}
|
||||
}
|
||||
|
||||
private Program linkVariant(String name, Map<PatchShaderType, String> transformed, InputAvailability availability, boolean shadow) {
|
||||
final ProgramBuilder builder = beginBuilder(name, transformed);
|
||||
wireGbufferProgram(builder, availability, shadow);
|
||||
final Program variant = builder.build();
|
||||
vertexWriteReplays.attach(name, variant, transformed, (samplers, images) -> wireGbufferResources(samplers, images, availability, shadow));
|
||||
this.customUniforms.mapholderToPass(builder, variant);
|
||||
return variant;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Program buildInstancedVariant(String sourceName, InputAvailability availability, boolean shadow, Instancing kind) {
|
||||
final Map<Pair<String, InputAvailability>, Map<PatchShaderType, String>> transforms = instancedAttributeTransforms.get(kind);
|
||||
@@ -1605,12 +1690,7 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
final ProgramBuilder builder = beginBuilder(sourceName + kind.variantSuffix, transformed);
|
||||
wireGbufferProgram(builder, availability, shadow);
|
||||
final Program variant = builder.build();
|
||||
vertexWriteReplays.attach(sourceName + kind.variantSuffix, variant, transformed, (samplers, images) -> wireGbufferResources(samplers, images, availability, shadow));
|
||||
this.customUniforms.mapholderToPass(builder, variant);
|
||||
return variant;
|
||||
return linkVariant(sourceName + kind.variantSuffix, transformed, availability, shadow);
|
||||
} catch (Exception e) {
|
||||
Iris.logger.warn("{} instanced variant link failed for {}; disabling it", kind, sourceName, e);
|
||||
supportsInstancing[kind.ordinal()] = false;
|
||||
@@ -1641,50 +1721,52 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
|
||||
@Override
|
||||
public boolean hasInstancedVariant(Instancing kind) {
|
||||
return current != null && current.instancedVariant(kind) != null;
|
||||
return current != null && current.select(kind, false) != null;
|
||||
}
|
||||
|
||||
public boolean hasInstancedVariant(RenderCondition condition, InputAvailability availability, Instancing kind) {
|
||||
final Pass pass = table.match(condition, availability);
|
||||
return pass != null && pass.instancedVariant(kind) != null;
|
||||
return pass != null && pass.select(kind, false) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindInstancedVariant(Instancing kind) {
|
||||
activeInstancing = kind;
|
||||
final Program variant = current.instancedVariant(kind);
|
||||
variant.use();
|
||||
this.customUniforms.push(variant);
|
||||
current.bindSelected();
|
||||
}
|
||||
|
||||
private record WideLineProgram(Program program, int lineWidthLocation, int screenSizeLocation) {}
|
||||
|
||||
private record WideLineKey(String sourceName, InputAvailability availability, boolean shadow, boolean clipped) {}
|
||||
|
||||
@Nullable
|
||||
private WideLineProgram sharedWideLineProgram(String sourceName, InputAvailability availability, boolean shadow) {
|
||||
final SharedProgramKey key = new SharedProgramKey(sourceName, availability, shadow);
|
||||
private WideLineProgram sharedWideLineProgram(ProgramSource source, InputAvailability availability, boolean shadow, boolean clipped) {
|
||||
final WideLineKey key = new WideLineKey(source.getName(), availability, shadow, clipped);
|
||||
if (wideLinePrograms.containsKey(key)) return wideLinePrograms.get(key);
|
||||
final WideLineProgram built = buildWideLineProgram(sourceName, availability, shadow);
|
||||
final WideLineProgram built = clipped ? buildClippedWideLineProgram(source, availability, shadow)
|
||||
: buildWideLineProgram(source.getName() + "_widelines", wideLineTransforms.get(Pair.of(source.getName(), availability)), availability, shadow);
|
||||
wideLinePrograms.put(key, built);
|
||||
return built;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private WideLineProgram buildWideLineProgram(String sourceName, InputAvailability availability, boolean shadow) {
|
||||
final Map<PatchShaderType, String> transformed = wideLineTransforms.get(Pair.of(sourceName, availability));
|
||||
private WideLineProgram buildClippedWideLineProgram(ProgramSource source, InputAvailability availability, boolean shadow) {
|
||||
final String name = source.getName() + "_widelines_clipped";
|
||||
return buildClipped(source, name, (vertex, fragment) ->
|
||||
buildWideLineProgram(name, TransformPatcher.patchAttributesWideLines(vertex, null, null, null, fragment, availability, true), availability, shadow));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private WideLineProgram buildWideLineProgram(String name, @Nullable Map<PatchShaderType, String> transformed, InputAvailability availability, boolean shadow) {
|
||||
if (transformed == null) {
|
||||
return null;
|
||||
}
|
||||
final String name = sourceName + "_widelines";
|
||||
try {
|
||||
final ProgramBuilder builder = beginBuilder(name, transformed);
|
||||
wireGbufferProgram(builder, availability, shadow);
|
||||
final Program program = builder.build();
|
||||
vertexWriteReplays.attach(name, program, transformed, (samplers, images) -> wireGbufferResources(samplers, images, availability, shadow));
|
||||
this.customUniforms.mapholderToPass(builder, program);
|
||||
final Program program = linkVariant(name, transformed, availability, shadow);
|
||||
final int id = program.getProgramId();
|
||||
return new WideLineProgram(program, GLSMPrograms.glGetUniformLocation(id, "iris_LineWidth"), GLSMPrograms.glGetUniformLocation(id, "iris_ScreenSize"));
|
||||
} catch (Exception e) {
|
||||
Iris.logger.warn("Wide line program link failed for {}; lines there stay 1 px wide", sourceName, e);
|
||||
Iris.logger.warn("Wide line program link failed for {}; lines there stay 1 px wide", name, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1698,8 +1780,8 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
}
|
||||
GLSMHooks.resolvePendingProgram();
|
||||
final Pass pass = current;
|
||||
if (pass == null || pass.getProgram() == null || activeInstancing != Instancing.NONE
|
||||
|| GLSMPrograms.getActiveProgram() != pass.getProgram().getProgramId()) {
|
||||
final Program bound = pass != null ? pass.boundProgram() : null;
|
||||
if (bound == null || activeInstancing != Instancing.NONE || GLSMPrograms.getActiveProgram() != bound.getProgramId()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1709,7 +1791,7 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
return false;
|
||||
}
|
||||
final boolean needsWidening = shadow || !packWidensLines;
|
||||
final WideLineProgram wide = needsWidening ? linePass.wideLineProgram() : null;
|
||||
final WideLineProgram wide = needsWidening ? linePass.wideLineProgram(GLSMRasterizer.clipEmulationActive()) : null;
|
||||
if (needsWidening && wide == null) {
|
||||
return false;
|
||||
}
|
||||
@@ -1742,11 +1824,11 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
}
|
||||
|
||||
public void compileWideLinePrograms() {
|
||||
table.match(RenderCondition.LINES, LINE_INPUTS).wideLineProgram();
|
||||
table.match(RenderCondition.LINES, LINE_INPUTS).wideLineProgram(false);
|
||||
final Pass shadowPass = table.match(RenderCondition.SHADOW, SHADOW_INPUTS);
|
||||
if (shadowPass != null) shadowPass.wideLineProgram();
|
||||
if (shadowPass != null) shadowPass.wideLineProgram(false);
|
||||
final Pass shadowWaterPass = table.match(RenderCondition.SHADOW_TRANSLUCENT, SHADOW_INPUTS);
|
||||
if (shadowWaterPass != null) shadowWaterPass.wideLineProgram();
|
||||
if (shadowWaterPass != null) shadowWaterPass.wideLineProgram(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1851,7 +1933,7 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
table.forEach(pass -> {
|
||||
if (pass == null || !visited.add(pass)) return;
|
||||
for (Instancing kind : Instancing.VALUES) {
|
||||
if (kind != Instancing.NONE) pass.instancedVariant(kind);
|
||||
if (kind != Instancing.NONE) pass.select(kind, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -2667,7 +2749,7 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
source.getTessControlSource().orElse(null),
|
||||
source.getTessEvalSource().orElse(null),
|
||||
source.getFragmentSource().orElse(null),
|
||||
inputs)));
|
||||
inputs, false)));
|
||||
}
|
||||
|
||||
private static final ProgramId[] INSTANCED_PROGRAM_IDS = {
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package net.coderbot.iris.pipeline.transform;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GlslTransformUtils;
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.ClipDistanceGlsl;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.ShaderType;
|
||||
import net.coderbot.iris.pipeline.transform.parameter.AttributeParameters;
|
||||
import net.coderbot.iris.pipeline.transform.parameter.Parameters;
|
||||
import org.taumc.glsl.Transformer;
|
||||
import org.taumc.glsl.grammar.GLSLParser;
|
||||
@@ -123,7 +126,9 @@ public class CommonTransformer {
|
||||
});
|
||||
}
|
||||
|
||||
if (parameters.patch == Patch.ATTRIBUTES && parameters.type == ShaderType.VERTEX) {
|
||||
if (parameters.patch == Patch.ATTRIBUTES && RenderSystem.isClipDistanceEmulated()) {
|
||||
injectClipDiscard(root, (AttributeParameters) parameters);
|
||||
} else if (parameters.patch == Patch.ATTRIBUTES && parameters.type == ShaderType.VERTEX) {
|
||||
root.injectVariable("uniform bool angelica_ClipPlanesEnabled;");
|
||||
root.injectVariable("uniform vec4 angelica_ClipPlane[8];");
|
||||
root.appendMain(
|
||||
@@ -144,4 +149,31 @@ public class CommonTransformer {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static final String CLIP_VS_BLOCK;
|
||||
static {
|
||||
final String[] distances = new String[8];
|
||||
for (int i = 0; i < distances.length; i++) {
|
||||
distances[i] = "dot(angelica_ClipPlane[" + i + "], _cp_ep)";
|
||||
}
|
||||
final StringBuilder sb = new StringBuilder(256).append("{ vec4 _cp_ep = iris_ModelViewMatrix * iris_Vertex;\n");
|
||||
ClipDistanceGlsl.appendVsAssign(sb, distances);
|
||||
CLIP_VS_BLOCK = sb.append('}').toString();
|
||||
}
|
||||
|
||||
private static void injectClipDiscard(Transformer root, AttributeParameters parameters) {
|
||||
if (!parameters.clipDiscard) {
|
||||
return;
|
||||
}
|
||||
if (parameters.type == ShaderType.VERTEX) {
|
||||
root.injectVariable("uniform vec4 angelica_ClipPlane[8];");
|
||||
root.injectVariable(ClipDistanceGlsl.VS_OUT_DIST0);
|
||||
root.injectVariable(ClipDistanceGlsl.VS_OUT_DIST1);
|
||||
root.appendMain(CLIP_VS_BLOCK);
|
||||
} else if (parameters.type == ShaderType.FRAGMENT) {
|
||||
root.injectVariable(ClipDistanceGlsl.FS_IN_DIST0);
|
||||
root.injectVariable(ClipDistanceGlsl.FS_IN_DIST1);
|
||||
root.prependMain(ClipDistanceGlsl.FS_DISCARD);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -238,7 +238,7 @@ public class ShaderTransformer {
|
||||
final ShaderDiskCache.Key k = ShaderDiskCache.key("iris-transform").str(patch.name());
|
||||
parameters.appendDiskKey(k);
|
||||
k.i(RenderSystem.getMaxGlslVersion()).b(RenderSystem.supportsSSBO()).b(RenderSystem.supportsImageLoadStore())
|
||||
.b(RenderSystem.isGLES()).b(BackendManager.RENDER_BACKEND.isSDLGPU()).b(BackendManager.RENDER_BACKEND.supportsComputeImageAtomics()).str(RwImageStoreExtractor.atomicCustomImagesKey())
|
||||
.b(RenderSystem.isGLES()).b(RenderSystem.isClipDistanceEmulated()).b(BackendManager.RENDER_BACKEND.isSDLGPU()).b(BackendManager.RENDER_BACKEND.supportsComputeImageAtomics()).str(RwImageStoreExtractor.atomicCustomImagesKey())
|
||||
.i(maxSupportedHoistVersion);
|
||||
if (enabledRequirements == null) {
|
||||
k.i(-1);
|
||||
|
||||
@@ -143,11 +143,15 @@ public class TransformPatcher {
|
||||
}
|
||||
|
||||
public static Map<PatchShaderType, String> patchAttributesInstanced(String vertex, String geometry, String tessControl, String tessEval, String fragment, InputAvailability inputs, boolean scrollGlint, Instancing instancing) {
|
||||
return transform(vertex, geometry, tessControl, tessEval, fragment, new AttributeParameters(Patch.ATTRIBUTES, geometry != null, tessControl != null, inputs, scrollGlint, instancing));
|
||||
return patchAttributesInstanced(vertex, geometry, tessControl, tessEval, fragment, inputs, scrollGlint, instancing, false);
|
||||
}
|
||||
|
||||
public static Map<PatchShaderType, String> patchAttributesWideLines(String vertex, String geometry, String tessControl, String tessEval, String fragment, InputAvailability inputs) {
|
||||
return transform(vertex, geometry, tessControl, tessEval, fragment, new AttributeParameters(Patch.ATTRIBUTES, geometry != null, tessControl != null, inputs, false, Instancing.NONE, true));
|
||||
public static Map<PatchShaderType, String> patchAttributesInstanced(String vertex, String geometry, String tessControl, String tessEval, String fragment, InputAvailability inputs, boolean scrollGlint, Instancing instancing, boolean clipDiscard) {
|
||||
return transform(vertex, geometry, tessControl, tessEval, fragment, new AttributeParameters(Patch.ATTRIBUTES, geometry != null, tessControl != null, inputs, scrollGlint, instancing, false, clipDiscard));
|
||||
}
|
||||
|
||||
public static Map<PatchShaderType, String> patchAttributesWideLines(String vertex, String geometry, String tessControl, String tessEval, String fragment, InputAvailability inputs, boolean clipDiscard) {
|
||||
return transform(vertex, geometry, tessControl, tessEval, fragment, new AttributeParameters(Patch.ATTRIBUTES, geometry != null, tessControl != null, inputs, false, Instancing.NONE, true, clipDiscard));
|
||||
}
|
||||
|
||||
public static Map<PatchShaderType, String> patchAttributes(String vertex, String geometry, String tessControl, String tessEval, String fragment, InputAvailability inputs) {
|
||||
|
||||
+8
-3
@@ -13,13 +13,14 @@ public class AttributeParameters extends Parameters {
|
||||
public final boolean scrollGlint;
|
||||
public final Instancing instancing;
|
||||
public final boolean wideLines;
|
||||
public final boolean clipDiscard;
|
||||
// WARNING: adding new fields requires updating hashCode and equals methods!
|
||||
|
||||
public AttributeParameters(Patch patch, boolean hasGeometry, boolean hasTesselation, InputAvailability inputs, boolean scrollGlint, Instancing instancing) {
|
||||
this(patch, hasGeometry, hasTesselation, inputs, scrollGlint, instancing, false);
|
||||
this(patch, hasGeometry, hasTesselation, inputs, scrollGlint, instancing, false, false);
|
||||
}
|
||||
|
||||
public AttributeParameters(Patch patch, boolean hasGeometry, boolean hasTesselation, InputAvailability inputs, boolean scrollGlint, Instancing instancing, boolean wideLines) {
|
||||
public AttributeParameters(Patch patch, boolean hasGeometry, boolean hasTesselation, InputAvailability inputs, boolean scrollGlint, Instancing instancing, boolean wideLines, boolean clipDiscard) {
|
||||
super(patch, null);
|
||||
this.hasGeometry = hasGeometry;
|
||||
this.hasTesselation = hasTesselation;
|
||||
@@ -27,6 +28,7 @@ public class AttributeParameters extends Parameters {
|
||||
this.scrollGlint = scrollGlint;
|
||||
this.instancing = instancing;
|
||||
this.wideLines = wideLines;
|
||||
this.clipDiscard = clipDiscard;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -37,7 +39,7 @@ public class AttributeParameters extends Parameters {
|
||||
@Override
|
||||
public void appendDiskKey(ShaderDiskCache.Key k) {
|
||||
super.appendDiskKey(k);
|
||||
k.b(hasGeometry).b(hasTesselation).str(inputs == null ? null : inputs.name()).b(scrollGlint).str(instancing.name()).b(wideLines);
|
||||
k.b(hasGeometry).b(hasTesselation).str(inputs == null ? null : inputs.name()).b(scrollGlint).str(instancing.name()).b(wideLines).b(clipDiscard);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -49,6 +51,7 @@ public class AttributeParameters extends Parameters {
|
||||
result = prime * result + (scrollGlint ? 1231 : 1237);
|
||||
result = prime * result + instancing.ordinal();
|
||||
result = prime * result + (wideLines ? 1231 : 1237);
|
||||
result = prime * result + (clipDiscard ? 1231 : 1237);
|
||||
result = prime * result + ((inputs == null) ? 0 : inputs.hashCode());
|
||||
return result;
|
||||
}
|
||||
@@ -72,6 +75,8 @@ public class AttributeParameters extends Parameters {
|
||||
return false;
|
||||
if (wideLines != other.wideLines)
|
||||
return false;
|
||||
if (clipDiscard != other.clipDiscard)
|
||||
return false;
|
||||
if (inputs == null) {
|
||||
return other.inputs == null;
|
||||
} else return inputs.equals(other.inputs);
|
||||
|
||||
@@ -381,6 +381,12 @@ options.angelica.tracy.captureAction.tooltip=Starts or stops a Tracy capture. Sa
|
||||
options.angelica.tracy.captureAction.start=Start capture
|
||||
options.angelica.tracy.captureAction.stop=Stop capture
|
||||
options.angelica.tracy.captureAction.saving=Saving...
|
||||
options.angelica.tracy.frameImages=Frame Images
|
||||
options.angelica.tracy.frameImages.tooltip=Attach small screenshots to Tracy frames for playback in Tracy Profiler. Only captured while a profiler is connected.
|
||||
options.angelica.tracy.frameImageRate=Frame Image Rate
|
||||
options.angelica.tracy.frameImageRate.tooltip=Frame images captured per second while connected.
|
||||
options.angelica.tracy.frameImageRate.value=%s/s
|
||||
options.angelica.tracy.frameImageRate.everyFrame=Every frame
|
||||
|
||||
pack.iris.select.title=Select
|
||||
pack.iris.configure.title=Configure
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
options.button.shader=Shadery...
|
||||
|
||||
options.chunks=%s chunków
|
||||
options.entityShadows=Cienie bytów
|
||||
options.entityShadows=Cienie mobów
|
||||
options.droppedItems=%s przedmiotów
|
||||
options.droppedItems.max=Bez limitu
|
||||
|
||||
sodium.option_impact.low=Niski
|
||||
sodium.option_impact.medium=Średni
|
||||
sodium.option_impact.high=Wysoki
|
||||
@@ -10,56 +13,107 @@ sodium.option_impact.varies=Różny
|
||||
sodium.options.pages.quality=Jakość
|
||||
sodium.options.pages.performance=Wydajność
|
||||
sodium.options.pages.advanced=Zaawansowane
|
||||
sodium.options.view_distance.tooltip=Zasięg renderowania kontroluje, jak daleko otoczenie będzie renderowane. Krótszy dystans oznacza, że mniej otoczenia będzie renderowane, zwiększając klatki na sekundę.
|
||||
sodium.options.simulation_distance.tooltip=Zasięg symulacji kontroluje jak daleko otoczenie i byty będą wczytywane i symulowane. Krótsze dystansy mogą zmniejszyć obciążenie wewnętrznego serwera i poprawić klatki na sekundę.
|
||||
sodium.options.brightness.tooltip=Kontroluje jasność (gammę) gry.
|
||||
sodium.options.pages.appearance=Wygląd
|
||||
sodium.options.pages.text=Efekty tekstu
|
||||
sodium.options.pages.fps_reducer=Ogranicznik FPS
|
||||
sodium.options.view_distance.tooltip=Zasięg renderowania określa, jak daleko otoczenie będzie renderowane. Krótszy dystans oznacza, że mniej otoczenia będzie renderowane, zwiększając klatki na sekundę.
|
||||
sodium.options.simulation_distance.tooltip=Zasięg symulacji określa, jak daleko otoczenie i byty będą wczytywane i symulowane. Krótsze dystansy mogą zmniejszyć obciążenie wewnętrznego serwera i poprawić klatki na sekundę.
|
||||
sodium.options.cc.vertical_view_distance.name=Pionowy zasięg widzenia
|
||||
sodium.options.cc.vertical_view_distance.tooltip=Pionowy zasięg widzenia określa pionowy zakres, w którym sześciany będą ładowane, wyśrodkowany względem gracza. Krótszy dystans oznacza, że mniej otoczenia będzie renderowane, zwiększając klatki na sekundę.
|
||||
sodium.options.brightness.tooltip=Określa jasność (gammę) gry.
|
||||
sodium.options.clouds.name=Chmury
|
||||
sodium.options.clouds.tooltip=Kontroluje, czy chmury będą lub nie będą widoczne.
|
||||
sodium.options.gui_scale.tooltip=Ustawia maksymalne skalowanie użyte dla interfejsu użytkownika. Jeśli opcja "auto" jest użyta, wtedy największa skala interfejsu zawsze będzie użyta.
|
||||
sodium.options.clouds.tooltip=Określa widoczność chmur.
|
||||
sodium.options.gui_scale.tooltip=Ustawia maksymalną skalę interfejsu użytkownika. Jeśli opcja "auto" jest użyta, zawsze stosowany będzie największy współczynnik skalowania.
|
||||
sodium.options.fullscreen.tooltip=Jeśli włączone, gra będzie wyświetlana w trybie pełnoekranowym (Jeśli opcja jest wspierana).
|
||||
sodium.options.v_sync.tooltip=Jeśli włączone, klatki na sekundę będą synchronizowane z częstotliwością odświeżania monitora, co zapewni płynniejsze wrażenia kosztem gorszego opóźnienia wejścia. To ustawienie może zmniejszyć wydajność, jeśli twój sprzęt jest zbyt słaby.
|
||||
sodium.options.fps_limit.tooltip=Ogranicza maksymalną liczbę klatek na sekundę. Ta opcja może pomóc zmniejszyć zużycie baterii i obciążenie systemu, gdy robisz wiele rzeczy naraz. Jeśli VSync jest włączony, limit klatek będzie zignorowany, jeśli jest mniejszy od częstotliwości odświeżania twojego monitora.
|
||||
sodium.options.view_bobbing.tooltip=Jeśli włączone, pole widzenia gracza będzie się bujać i podskakiwać. Gracze, którzy posiadają chorobę lokomocyjną lub tym podobne mogą mieć korzyści z wyłączenia tej opcji.
|
||||
sodium.options.attack_indicator.tooltip=Kontroluje, gdzie ikona ataku jest wyświetlana na ekranie.
|
||||
sodium.options.autosave_indicator.tooltip=Jeśli włączone, ikona zapisywania będzie pokazana, gdy gra zapisuje świat na dysku.
|
||||
sodium.options.graphics_quality.tooltip=Domyślna jakość grafiki kontroluje kilka starych opcji i jest potrzebna do kompatybilności modów. Jeśli opcje poniżej są ustawione na "Domyślna", wtedy użyją one tej opcji.
|
||||
sodium.options.clouds_quality.tooltip=Kontroluje jakość renderowania chmur na niebie.
|
||||
sodium.options.weather_quality.tooltip=Kontroluje jakość efektów deszczu i śniegu.
|
||||
sodium.options.v_sync.tooltip=Wył. prezentuje każdą klatkę natychmiastowo: Najniższe opóźnienie, ale występuje rozrwyanie obrazu. Wł. czeka na vblank i kolejkuje nową klatkę: Brak rozrwyania obrazu, większe opóźnienie. Mailbox czeka na vblank ale zamienia zakolejkowaną klatkę: Brak rozrwyania obrazu, opóźnienie zbliżone do trybu Wył.
|
||||
sodium.options.vsync_mode.off=Wył.
|
||||
sodium.options.vsync_mode.on=Wł.
|
||||
sodium.options.vsync_mode.mailbox=Mailbox
|
||||
sodium.options.fps_limit.tooltip=Ogranicza maksymalną liczbę klatek na sekundę. Ta opcja może pomóc zmniejszyć zużycie baterii i obciążenie systemu, gdy robisz wiele rzeczy naraz. Działa w każdym trybie VSync.
|
||||
sodium.options.view_bobbing.tooltip=Jeśli włączone, pole widzenia gracza będzie kołysać się i podskakiwać podczas poruszania się. Wyłączenie tej opcji może korzystnie wpłynąć na graczy z chorobą lokomocyjną.
|
||||
sodium.options.attack_indicator.tooltip=Określa, gdzie ikona ataku jest wyświetlana na ekranie.
|
||||
sodium.options.autosave_indicator.tooltip=Jeśli włączone, ikona zapisywania będzie widoczna, gdy gra zapisuje świat na dysku.
|
||||
sodium.options.graphics_quality.tooltip=Domyślna jakość grafiki określa kilka starszych opcji i jest niezbędna do zachowania kompatybilności z modami. Jeśli opcje poniżej są ustawione na "Domyślna", będą używać tej opcji.
|
||||
sodium.options.clouds_quality.tooltip=Określa jakość renderowania chmur na niebie.
|
||||
sodium.options.weather_quality.tooltip=Określa jakość efektów deszczu i śniegu.
|
||||
sodium.options.leaves_quality.name=Jakość liści
|
||||
sodium.options.leaves_quality.tooltip=Kontroluje jakość liści.
|
||||
sodium.options.leaves_quality.tooltip=Określa jakość liści.
|
||||
sodium.options.grass_quality.name=Jakość trawy
|
||||
sodium.options.grass_quality.tooltip=Kontroluje jakość stron bloków trawy.
|
||||
sodium.options.grass_quality.tooltip=Określa jakość stron bloków trawy.
|
||||
sodium.options.celeritas_smooth_lighting.name=Gładkie oświetlenie Celeritas
|
||||
sodium.options.celeritas_smooth_lighting.tooltip=Jeśli włączone, system oświetlenia Celeritas zostanie użyty do gładkiego oświetlenia. Działa tylko wtedy, kiedy gładkie oświetlenie jest włączone.
|
||||
sodium.options.texture_filtering.name=Filtrowanie tekstur
|
||||
sodium.options.texture_filtering.tooltip=Określa sposób próbkowania tekstur bloków w oddali.
|
||||
sodium.options.texture_filtering.rgss=RGSS
|
||||
sodium.options.texture_filtering.anisotropic=Anizotropowe
|
||||
sodium.options.texture_filtering.rgss_anisotropic=RGSS i anizotropowe
|
||||
sodium.options.texel_sampling.name=Wygładzanie tekseli
|
||||
sodium.options.texel_sampling.tooltip=Określa sposób rysowania granicy między dwoma tekselami tekstury bloku. "Najbliższe" jest ostre, "Liniowe" wygładza.
|
||||
sodium.options.texel_sampling.linear=Liniowe
|
||||
sodium.options.texel_sampling.nearest=Najbliższe
|
||||
sodium.options.anisotropic_filtering.name=Filtrowanie anizotropowe
|
||||
sodium.options.anisotropic_filtering.tooltip=Uwydatnia tekstury oglądane pod ostrym kątem.
|
||||
sodium.options.gui_block_supersampling.name=Supersampling bloków w GUI
|
||||
sodium.options.gui_block_supersampling.tooltip=Określa liczbę próbek tekstur na piksel dla trójwymiarowych ikon bloków w GUI.
|
||||
sodium.options.particle_quality.name=Jakość cząstek
|
||||
sodium.options.particle_quality.tooltip=Kontroluje maksymalną ilość cząstek, które mogą być widoczne na ekranie w tym samym czasie.
|
||||
sodium.options.smooth_lighting.tooltip=Kontroluje, czy bloki będą gładko oświetlone i cieniowane. Ta opcja może troszkę podwyższyć ilość czasu, jaką zajmuje załadowanie lub zaktualizowanie chunku, lecz nie ma wpływu na klatki na sekundę.This slightly increases the amount of time needed to re-build a chunk, but doesn't affect frame rates.
|
||||
sodium.options.particle_quality.tooltip=Określa maksymalną ilość cząstek, które mogą być widoczne na ekranie w tym samym czasie.
|
||||
sodium.options.smooth_lighting.tooltip=Określa, czy bloki będą gładko oświetlone i cieniowane. Ta opcja może troszkę podwyższyć ilość czasu, jaką zajmuje załadowanie lub zaktualizowanie chunku, lecz nie ma wpływu na klatki na sekundę.
|
||||
sodium.options.biome_blend.value=%s blok(ów)
|
||||
sodium.options.biome_blend.tooltip=Kontroluje zasięg, w którym biomy będą próbkowane dla kolorowania bloków. Wyższe wartoście znacznie zwiększy czas ładowania chunków, w zamian za znikome poprawki w jakości.
|
||||
sodium.options.entity_distance.tooltip=Kontroluje, jak daleko od gracza będą renderowane byty. Wyższe wartości zwiększają zasięg renderowania, kosztem klatek na sekundę.
|
||||
options.biomeBlendRadius=Zasięg przenikania biomów
|
||||
sodium.options.biome_blend_mode.name=Przenikanie bloków biomów
|
||||
sodium.options.biome_blend_mode.tooltip="Szybkie" stosuje jeden odcień biomu na każdy blok. "Dokładne" miesza odcień między trawą, liśćmi i powierzchnią wody używając zasięgu mieszania biomów.
|
||||
sodium.options.biome_blend.tooltip=Określa zasięg, w którym biomy będą próbkowane dla kolorowania trawy, liści, wody i nieba.
|
||||
sodium.options.entity_distance.tooltip=Określa, jak daleko od gracza będą renderowane byty. Wyższe wartości zwiększają zasięg renderowania, kosztem klatek na sekundę.
|
||||
sodium.options.entity_shadows.tooltip=Jeśli włączone, podstawowe cienie będą renderowane pod mobami i innymi bytami.
|
||||
sodium.options.vignette.name=Winieta
|
||||
sodium.options.vignette.tooltip=Jeśli włączone, efekt winiety będzie renderowany na widoku gracza. To raczej nie wpłynie na klatki na sekundę, chyba że jesteś ograniczany przez szybkość wypełniania.
|
||||
sodium.options.mipmap_levels.tooltip=Kontroluje ilość mipmap, które będą użyte do tekstur modeli bloków. Wyższe wartości skutkują lepszym renderowaniem bloków w oddali, lecz mogą spowodować pogorszenie wydajności przy dużej ilości animowanych tekstur.
|
||||
sodium.options.mipmap_levels.tooltip=Określa ilość mipmap, które będą użyte do tekstur modeli bloków. Wyższe wartości skutkują lepszą jakością bloków w oddali, lecz mogą spowodować pogorszenie wydajności przy dużej ilości animowanych tekstur.
|
||||
sodium.options.mipmap_generation.name=Generowanie mipmap
|
||||
sodium.options.mipmap_generation.tooltip=Wybiera, jak mniejsze warianty tekstur są generowane.
|
||||
sodium.options.mipmap_generation.vanilla_1_7_10=Minecraft 1.7.10
|
||||
sodium.options.mipmap_generation.celeritas=Celeritas
|
||||
sodium.options.mipmap_generation.modern_vanilla=Minecraft 26.2
|
||||
sodium.options.use_vertex_objects.name=Obiekty układu wierzchołków
|
||||
sodium.options.use_vertex_objects.tooltip=Pomaga poprawić wydajność poprzez przenoszenie informacji o tym, jak dane wierzchołków powinny być renderowane do sterownika, pozwalając na lepszą optymalizację dla wielokrotnego renderowania tych samych obiektów. Generalnie, nie ma potrzebyy wyłączania tego, chyba że używasz niekompatybilnych modów.
|
||||
sodium.options.use_block_face_culling.name=Chowanie ścian bloków
|
||||
sodium.options.use_block_face_culling.tooltip=Jeśli włączone, tylko ściany bloków, które odwrócone są w stronę kamery, będą renderowane. To może wyeliminować dużą liczbę ścian bloków wcześnie w procesie renderowania, oszczędzając przepustowość pamięci i czas na GPU. Niektóre paczki zasobów mogą mieć problem z tą opcją, możesz wtedy spróbować ją wyłączyć jeśli widzisz dziury w blokach.
|
||||
sodium.options.use_vertex_objects.tooltip=Pomaga poprawić wydajność poprzez przenoszenie informacji o tym, jak dane wierzchołków powinny być renderowane do sterownika, pozwalając na lepszą optymalizację dla wielokrotnego renderowania tych samych obiektów. Generalnie, nie ma potrzeby wyłączania tego, chyba że używasz niekompatybilnych modów.
|
||||
sodium.options.use_block_face_culling.name=Culling ścian bloków
|
||||
sodium.options.use_block_face_culling.tooltip=Jeśli włączone, tylko ściany bloków które odwrócone są w stronę kamery, będą renderowane. To może wyeliminować dużą liczbę ścian bloków wcześnie w procesie renderowania, oszczędzając przepustowość pamięci i czas na GPU. Niektóre paczki zasobów mogą mieć problem z tą opcją, możesz wtedy spróbować ją wyłączyć jeśli widzisz dziury w blokach.
|
||||
sodium.options.use_compact_vertex_format.name=Kompaktowy format wierzchołków
|
||||
sodium.options.use_compact_vertex_format.tooltip=Jeśli włączone, bardziej kompaktowy format wierzchołków będzie użyty do renderowania chunków. To może znacznie zmniejszyć zużycie pamięci grafiki i wymagań przepustowości, szczególnie dla zintegrowanych układów graficznych, ale może powodować z-fighting z niektórymi paczkami zasobów przez to jak zmniejsza precyzję pozycji i atrybutuów współrzędnych tekstur.
|
||||
sodium.options.use_fog_occlusion.name=Okluzja mgły
|
||||
sodium.options.use_fog_occlusion.tooltip=Jeśli włączone, chunki, które uważane są za w pełni ukryte przez mgłę, nie będą renderowane, zwiększając wydajność. Efekt poprawy może być większy, gdy efekt mgły jest cięższy (np. pod wodą), ale może skutkować w niechcianych artefaktach graficznych pomiędzy niebem a mgłą w niektórych przypadkach.
|
||||
sodium.options.use_entity_culling.name=Chowanie bytów
|
||||
sodium.options.use_fog_occlusion.tooltip=Jeśli włączone, chunki, które uważane są za w pełni ukryte przez mgłę, nie będą renderowane, zwiększając wydajność. Poprawa wydajności może być większa, gdy efekt mgły jest większy (np. pod wodą), ale może skutkować w niechcianych artefaktach graficznych pomiędzy niebem a mgłą w niektórych przypadkach.
|
||||
sodium.options.use_entity_culling.name=Culling bytów
|
||||
sodium.options.use_entity_culling.tooltip=Jeśli włączone, byty, które nie są w żadnym widocznym chunku, będą zignorowane podczas renderowaniu.To może zwiększyć wydajność, pomijając renderowanie bytów znajdujących się pod ziemią lub za ścianami.
|
||||
sodium.options.use_particle_culling.name=Chowanie cząstek
|
||||
sodium.options.use_particle_culling.tooltip=Jeśli włączone, tylko cząstki, które uważane są za widoczne, będą renderowane. To może skutkować znaczną poprawą klatek na sekundę, gdy wiele cząstek jest w polu widzenia.
|
||||
sodium.options.section_gated_tesr_culling.name=Bramkowany sekcyjnie culling TESR
|
||||
sodium.options.section_gated_tesr_culling.tooltip=Jeśli włączone, byty-bloki są chowane algorytmem frustum po jednej sekcji na raz, zamist po jednym bytcie na raz.
|
||||
sodium.options.use_particle_culling.name=Culling cząstek
|
||||
sodium.options.use_particle_culling.tooltip=Jeśli włączone, tylko cząstki, które uważane są za widoczne, będą renderowane. To może skutkować znaczną poprawą klatek na sekundę, gdy wiele cząstek znajduje się w polu widzenia.
|
||||
sodium.options.animate_only_visible_textures.name=Animuj tylko widoczne tekstury
|
||||
sodium.options.animate_only_visible_textures.tooltip=Jeśli włączone, tylko animowane tekstury, które uważane są za widoczne, będą aktualizowane. To może pomóc z wydajnością na niektórych systemach, szczególnie z cięższymi paczkami zasobów. Jeśli zauważyłeś, że niektóre tekstury nie są animowane, spróbuj wyłączyć tę opcję. If enabled, only animated textures determined to be visible will be updated.
|
||||
sodium.options.animate_only_visible_textures.tooltip=Jeśli włączone, tylko animowane tekstury, które uważane są za widoczne, będą aktualizowane. To może pomóc z wydajnością na niektórych systemach, szczególnie z cięższymi paczkami zasobów. Jeśli zauważyłeś, że niektóre tekstury nie są animowane, spróbuj wyłączyć tę opcję.
|
||||
sodium.options.translucency_sorting.name=Sortowanie przezroczystości
|
||||
sodium.options.translucency_sorting.tooltip=Jeśli włączone, przezroczyste bloki będą sortowane poprawnie od tyłu do przodu aby zapewnić lepsze renderowanie, szczególnie jeśli wiele przezroczystych bloków jest widocznych i ułożonych na sobie. Jest to trochę kosztowna operacja.
|
||||
sodium.options.translucency_sorting.tooltip=Jeśli włączone, przezroczysty teren (woda, kolorowe szkło, lód) i przezroczyste byty-bloki będą sortowane od tyłu do przodu, aby nakładające się warstwy były poprawnie wyświetlone. Wyłączenie tej opcji zwiększa wdyajność w sytuacjach z dużą ilością przezroczystych bloków, ale powoduje artefakty. Zmienienie tej opcji przeładowuje chunki.
|
||||
sodium.options.cpu_render_ahead_limit.name=Limit renderowania klatek z wyprzedzeniem przez CPU
|
||||
sodium.options.cpu_render_ahead_limit.tooltip=Określa maksymalną liczbę klatek na które CPU może czekać, aby zostały wyrenderowane przez GPU. Bardzo niskie lub wysokie wartości mogą powodować niestabilność klatek.
|
||||
sodium.options.cpu_render_ahead_limit.tooltip=Określa maksymalną liczbę klatek na które CPU może czekać, aby zostały wyrenderowane przez GPU. Bardzo niskie lub wysokie wartości mogą powodować niestabilność klatek. 0 wyłącza tą opcję; Backend SDL-GPU ignoruje ją i stosuje własne rozwiązanie.
|
||||
sodium.options.cpu_render_ahead_limit.value=%s klat(ki/ek)
|
||||
sodium.options.fps_reducer_enabled.name=Ogranicz klatki na sekundę w tle
|
||||
sodium.options.fps_reducer_enabled.tooltip=Zmniejsza liczbę klatek na sekundę i głośność, gdy gra działa w tle.
|
||||
sodium.options.unfocused_fps_limit.name=Klatki na sekundę na drugim planie
|
||||
sodium.options.unfocused_fps_limit.tooltip=Ograniczenie liczby klatek na sekundę, gdy inne okno jest na pierwszym planie.
|
||||
sodium.options.unfocused_volume.name=Głośność na drugim planie
|
||||
sodium.options.unfocused_volume.tooltip=Ograniczenie głośności, gdy inne okno jest na pierwszym planie.
|
||||
sodium.options.minimized_volume.name=Głośność zminimalizowana
|
||||
sodium.options.minimized_volume.tooltip=Ograniczenie głośności, gdy gra jest zminimalizowana.
|
||||
sodium.options.idle_timeout.name=Limit czasu bezczynności
|
||||
sodium.options.idle_timeout.tooltip=Liczba minut bezczynności przed zastosowaniem limitów bezczynności.
|
||||
sodium.options.idle_timeout.value=%s minut
|
||||
sodium.options.idle_timeout.off=Wył.
|
||||
sodium.options.idle_fps_limit.name=Liczba klatek na sekundę w stanie bezczynności
|
||||
sodium.options.idle_fps_limit.tooltip=Ograniczenie liczby klatek na sekundę w stanie bezczynności.
|
||||
sodium.options.idle_volume.name=Głośność w stanie bezczynności
|
||||
sodium.options.idle_volume.tooltip=Głośność w stanie bezczynności.
|
||||
sodium.options.limit_menu_fps.name=Ograniczenie liczby klatek na sekundę w menu
|
||||
sodium.options.limit_menu_fps.tooltip=Ogranicza liczbę klatek na sekundę do 30, gdy żaden świat nie jest załadowany.
|
||||
sodium.options.allow_direct_memory_access.name=Zezwól na bezpośredni dostęp do pamięci
|
||||
sodium.options.allow_direct_memory_access.tooltip=Jeśli włączone, niektóre krytyczne ścieżki kodu będą miały pozwolenie na użycie bezpośredniego dostępu do pamięci dla wydajności. To często znacznie zmniejsza obciążenie CPU do renderowania chunków i bytów, lecz może utrudnić diagnozę błędów i awarii. Powinieneś to wyłączyć tylko wtedy, jeśli zostałeś o to zapytany lub wiesz co robisz.
|
||||
sodium.options.allow_direct_memory_access.tooltip=Jeśli włączone, niektóre krytyczne ścieżki kodu będą miały pozwolenie na użycie bezpośredniego dostępu do pamięci dla zwiększenia wydajności. To często znacznie zmniejsza obciążenie CPU podczas renderowania chunków i bytów, lecz może utrudnić diagnozę błędów i awarii. Powinieneś to wyłączyć tylko wtedy, jeśli zostałeś o to zapytany lub wiesz co robisz.
|
||||
sodium.options.ignore_driver_blacklist.name=Ignoruj czarną listę sterowników
|
||||
sodium.options.ignore_driver_blacklist.tooltip=Jeśli włączone, znane problemy z kompatybilnością z twoim systemem/sterownikiem będą zignorowane, pozwalając na włączenie opcji, które mogą powodować problemy z grą. Nie powinieneś dotykać tej opcji, chyba że dokładnie wiesz co robisz. Po zmienieniu tej opcji, musisz zapisać, zamknąć, a potem ponownie otworzyć ekran ustawień.
|
||||
sodium.options.enable_memory_tracing.name=Włącz śledzenie pamięci
|
||||
@@ -74,22 +128,49 @@ sodium.options.chunk_memory_allocator.swap=Swap
|
||||
sodium.options.chunk_update_threads.name=Wątki aktualizacji chunków
|
||||
sodium.options.chunk_update_threads.tooltip=Określa liczbę wątków użytych do budowania chunków. Używanie większej liczby wątków może przyspieszyć wczytywanie chunków i ich aktualizowanie, ale może negatywnie wpłynąć na klatki.
|
||||
sodium.options.always_defer_chunk_updates.name=Zawsze opóźniaj aktualizacje chunków
|
||||
sodium.options.always_defer_chunk_updates.tooltip=Jeśli włączone, renderowanie nigdy nie będzie czekać aż aktualizacje chunków się zakończą, nawet gdy są one ważne. To może znacznie poprawić wydajność klatek w niektórych przypadkach, ale może skutkować widocznym opóźnieniem na świecie.
|
||||
sodium.options.always_defer_chunk_updates.tooltip=Jeśli włączone, renderowanie nigdy nie będzie czekać aż aktualizacje chunków się zakończą, nawet gdy są one ważne. To może znacznie poprawić wydajność klatek w niektórych przypadkach, ale może skutkować widocznym opóźnieniem w świecie.
|
||||
sodium.options.use_no_error_context.name=Brak kontekstu błędu
|
||||
sodium.options.use_no_error_context.tooltip=Jeśli włączone, kontekst OpenGL zostanie stworzony z wyłączonym sprawdzaniem błędów. To może minimalnie zwiększyć wydajność, ale również zwiększa ryzyko awarii gry zamiast radzenia sobie z błędami OpenGL. Powinieneś wyłączyć tą opcję, jeśli napotykasz nagłe i niewyjaśnione awarie.
|
||||
sodium.options.async_occlusion_mode.name=Asynchroniczna okluzja
|
||||
sodium.options.async_occlusion_mode.tooltip=Określa, który przebieg renderowania używa asynchronicznej okluzji otoczenia. "Tylko cienie" (domyślnie) używa asynchronicznej okluzji tylko dla przebiegu cienia, zachowując synchroniczność głównej listy renderowania. "Wszystko" uruchamia wszystkie przebiegi w trybie asynchronicznym, co może spowodować opóźnienie widocznośći jednej klatki podczas szybkich ruchów kamery. "Żadna" całkowicie wyłącza asynchroniczną okluzję.
|
||||
sodium.options.async_occlusion_mode.only_shadow=Tylko cienie
|
||||
sodium.options.async_occlusion_mode.everything=Wszystko
|
||||
sodium.options.use_occlusion_culling.name=Culling okluzją
|
||||
sodium.options.use_occlusion_culling.tooltip=Jeśli włączone, chunki ukryte za innymi chunkami nie będą renderowane. Wyłączenie tej opcji znacznie zmniejszy wydajność, ale może być przydatne przy debugowaniu.
|
||||
sodium.options.threads.value=%s wątek(ów)
|
||||
sodium.options.default=Domyślne
|
||||
sodium.options.none=Żadne
|
||||
sodium.options.buttons.undo=Cofnij
|
||||
sodium.options.buttons.apply=Zastosuj
|
||||
sodium.options.buttons.donate=Kup kawę dla JellySquid!
|
||||
sodium.options.anaglyph.tooltip=Jeśli włączone, świat będzie renderowany dwukrotnie z czerwonymi/cyjanowymi maskami koloru.
|
||||
sodium.options.show_cape.tooltip=Jeśli włączone, twoja peleryna będzie widoczna.
|
||||
rso.search_bar_empty=Wyszukaj opcję...
|
||||
|
||||
options.dynamic_fov=Dynamiczne pole widzenia
|
||||
options.dynamic_fov.tooltip=Jeśli włączone, pole widzenia gry będzie zmieniać się na podstawie szybkości gracza.
|
||||
options.framerate=%s FPS
|
||||
|
||||
iris.shaders.toggled=Przełączono shadery na %s!
|
||||
iris.shaders.disabled=Shadery wyłączone!
|
||||
iris.shaders.reloaded=Shadery przeładowane!
|
||||
iris.shaders.reloaded.failure=Nie udało się przeładować shaderów! Powód: %s
|
||||
iris.shaders.toggled.failure=Nie udało się przełączyć shaderów! Powód: %s
|
||||
|
||||
key.category.iris.keybinds=Skróty klawiszowe Iris
|
||||
iris.keybind.reload=Przeładuj shadery
|
||||
iris.keybind.toggleShaders=Przełącz shadery
|
||||
iris.keybind.shaderPackSelection=Ekran wyboru shaderów
|
||||
key.categories.angelica=Angelica
|
||||
angelica.keybind.zoom=Zoom
|
||||
angelica.keybind.fps_reducer_force=Wymuś ogranicznik FPS
|
||||
angelica.keybind.fps_reducer_disable=Przełącz ogranicznik FPS
|
||||
angelica.keybind.tracyCapture=Zacznij/Zatrzymaj przechwytywanie Tracy
|
||||
angelica.fps_reducer.hud.forced=Ogranicznik FPS: wymuszony
|
||||
angelica.fps_reducer.hud.disabled=Ogranicznik FPS: wyłączony
|
||||
|
||||
options.iris.apply=Zastosuj
|
||||
options.iris.refresh=Odśwież
|
||||
options.iris.openShaderPackFolder=Otwórz folder paczek shaderów...
|
||||
options.iris.openShaderPackFolder=Otwórz folder z shaderami...
|
||||
options.iris.shaderPackSettings=Ustawnienia paczki shaderów...
|
||||
options.iris.shaderPackList=Lista paczek shaderów...
|
||||
options.iris.refreshShaderPacks=Odśwież paczki shaderów
|
||||
@@ -100,13 +181,21 @@ options.iris.shaderPackSelection.addedPacks=Dodano %s paczki shaderów
|
||||
options.iris.shaderPackSelection.failedAdd=Pliki nie były prawidłowymi paczkami shaderów
|
||||
options.iris.shaderPackSelection.failedAddSingle="%s" nie jest prawidłową paczką shaderów
|
||||
options.iris.shaderPackSelection.copyError=Nie można było dodać paczki shaderów "%s"
|
||||
options.iris.shaderPackSelection.copyErrorAlreadyExists="%s" już istnieje w twoim folderze Shader Packs!
|
||||
options.iris.shaderPackSelection.copyErrorAlreadyExists="%s" jest już w twoim folderze paczek shaderów!
|
||||
options.iris.shaderPackOptions.noOptions="%s" nie ma konfigurowalnych ustawień
|
||||
options.iris.shaderPackOptions.tooManyFiles=Nie można zaimportować wielu plików ustawień shadera!
|
||||
options.iris.shaderPackOptions.failedImport=Nie udało się zaimportować plik ustawień shadera "%s"
|
||||
options.iris.shaderPackOptions.failedImport=Nie udało się zaimportować pliku ustawień shadera "%s"
|
||||
options.iris.shaderPackOptions.importedSettings=Zaimportowano ustawienia shadera z "%s"
|
||||
options.iris.shaders.disabled=Shadery: Wyłączone
|
||||
options.iris.shaders.enabled=Shadery: Włączone
|
||||
options.iris.shaders.nonePresent=Shadery: Brak paczek shaderów
|
||||
options.iris.saveCompiledShaders.on=Zapisz skompilowane shadery: Wł.
|
||||
options.iris.saveCompiledShaders.off=Zapisz skompilowane shadery: Wył.
|
||||
options.iris.saveCompiledShaders.tooltip=Zapisuje skompilowane programy shaderów Iris na dysku, aby restartowanie i wchodzenie do nowych wymiarów ładowało je zamiast kompilowania na miejscu. Gdy ta opcja jest włączona, każda zmiana ustawień shadera kompiluje je dla każdego wymiaru na raz, przez co stosowanie ich dłużej trwa. Wyłączenie tej opcji usuwa zapisane programy.
|
||||
options.iris.saveCompiledShaders.unsupported=Twój sterownik graficzny nie może zapisać skompilowanych programów, więc ta opcja tylko przygotowuje shadery każdego wymiaru wcześniej.
|
||||
options.iris.saveCompiledShaders.progress=Kompilowanie shaderów
|
||||
options.iris.saveCompiledShaders.progressStep=Folder shaderów %s z %s: %s
|
||||
options.iris.saveCompiledShaders.baseFolder=(domyślne shadery)
|
||||
options.iris.back=Powrót
|
||||
options.iris.reset=Resetuj
|
||||
options.iris.reset.tooltip=Zresetować WSZYSTKIE opcje i zastosować?
|
||||
@@ -118,13 +207,334 @@ options.iris.setToDefault=Ustawić opcję na domyślną wartość?
|
||||
options.iris.profile=Profil
|
||||
options.iris.profile.custom=Niestandardowy
|
||||
options.iris.shadowDistance=Maksymalny zasięg cieni
|
||||
options.iris.shadowDistance.sodium_tooltip=Maksymalny zasięg renderowania cieni
|
||||
options.iris.shadowDistance.enabled=Pozwala zmienić maksymalny zasięg renderowania cieni. Teren i byty poza tym dystansem nie będą rzucać cieni. Zmniejszenie zasięgu renderowania cieni może znacznie zwiększyć wydajność.
|
||||
options.iris.shadowDistance.disabled=Twoja obecna paczka shaderów ustawiła już zasięg renderowania cieni; nie możesz go zmienić.
|
||||
options.iris.gui.hide=Ukryj GUI
|
||||
options.iris.gui.show=Pokaż GUI
|
||||
options.iris.search.button=Wyszukaj
|
||||
options.iris.clear.button=Wyczyść
|
||||
options.iris.search.tooltip=Ustawienia szukania shaderów (Ctrl + F)
|
||||
options.iris.clear.tooltip=Wyjdź z trybu szukania shaderów (Esc)
|
||||
|
||||
options.dynamic_lights=Dynamiczne światła
|
||||
options.dynamic_lights.tooltip=Włącza dynamiczne światła. Tryby: OFF. - wyłączone, FASTEST - opóźnienie 500ms, FAST - opóźnienie 250ms, FANCY - brak opóźnienia. Wyłączone, jeśli shadery są w użyciu.
|
||||
options.dynamic_lights.tooltip=Włącza dynamiczne światła. Tryby: OFF - wyłączone, FASTEST - opóźnienie 500ms, FAST - 250ms delay, FANCY - opóźnienie 50ms, REALTIME - brak opóźnienia.
|
||||
options.dynamic_lights_shader_force=Dynamiczne światła - Wymuszone z shaderami
|
||||
options.dynamic_lights_shader_force.tooltip=Włączone - Dynamiczne światła są widoczne nawet jeśli shader jest aktywny. Wyłączone - dynamiczne światła są wyłączone gdy shader jest aktywny.
|
||||
options.dynamic_lights_frustum_culling=Algorytm chowania frustum
|
||||
options.dynamic_lights_frustum_culling.tooltip=Przebudowuje chunki jedynie dla widocznych źródeł światła. Zmniejsza ilość pracy GPU dla świateł znajdujących się poza widokiem.
|
||||
options.dynamic_lights_adaptive_ticking=Adaptacyjne ticki
|
||||
options.dynamic_lights_adaptive_ticking.tooltip=Odświeża odległe i niewidoczne źródła światła z mniejszą częstotliwością. Działa razem z globalnym ustawieniem trybu.
|
||||
options.dynamic_lights_slow_dist=Zasięg powolnego odświeżania.
|
||||
options.dynamic_lights_slow_dist.tooltip=Zasięg w blokach gdzie światła są odświeżane z częstotliwością 1/5. Zalecane: 32 bloki (~2 chunki).
|
||||
options.dynamic_lights_slower_dist=Zasięg wolniejszego odświeżania.
|
||||
options.dynamic_lights_slower_dist.tooltip=Zasięg w blokach gdzie światła są odświeżane z częstotliwością 1/10. Zalecane: 64 bloki (~4 chunki).
|
||||
options.dynamic_lights_background_dist=Zasięg odświeżania w tle.
|
||||
options.dynamic_lights_background_dist.tooltip=Zasięg w blokach gdzie światła są odświeżane z częstotliwością 1/20 (raz na sekundę). Również używane do świateł poza widokiem.
|
||||
options.dynamic_lights_cull_timeout=Limit czasu chowania (ticki)
|
||||
options.dynamic_lights_cull_timeout.tooltip=Maksymalna ilość ticków ukryty chunk może czekać zanim zostanie zmuszony do przebudowania. Mniejsza = większa responsywność ale więcej pracy dla GPU.
|
||||
options.dynamiclights.page=Dynamiczne światła
|
||||
options.dynamiclights.entities=Konfiguruj byty...
|
||||
options.dynamiclights.entities.tooltip=Wybierz, które typy bytów mogą emitować dynamiczne światła.
|
||||
options.dynamiclights.entities.title=Dynamiczne światła - byty
|
||||
options.dynamiclights.entities.info=Przełącz które typy bytów mogą emitować dynamiczne światła. Niektóre wpisy służą zapewnieniu zgodności z modami.
|
||||
options.dynamiclights.entities.search=Wyszukaj (@mod, $on, $off)
|
||||
options.dynamiclights.entities.enableall=Włącz wszystkie
|
||||
options.dynamiclights.entities.disableall=Wyłącz wszystkie
|
||||
options.dynamiclights.entities.enableshown=Włącz widoczne
|
||||
options.dynamiclights.entities.disableshown=Wyłącz widoczne
|
||||
|
||||
options.angelica.search.no_results=Brak pasujących opcji
|
||||
options.angelica.fontconfig=Ustawienia czcionki...
|
||||
options.angelica.fontconfig.title=Ustawienia czcionki
|
||||
options.angelica.fontconfig.currentfonts=Główna czcionka: §e%s§r Zastępcza czcionka: §b%s
|
||||
options.angelica.fontconfig.searchprompt=§7§oWyszukaj czcionki...
|
||||
options.angelica.fontconfig.testareaprompt=§7§oObszar testowy...
|
||||
options.angelica.fontconfig.testareainfo=Wpisz poniżej tekst, aby sprawdzić, jak się wyświetla. Do formatowania używaj znaku &.
|
||||
options.angelica.fontconfig.testareaexample=&d&lHerobrine'a&r &ntutaj &7&m&oNIE&r było
|
||||
options.angelica.fontconfig.font_selection_info=Kliknij lewym przyciskiem myszy pozycję na liście, aby ustawić czcionkę główną. Kliknij prawym przyciskiem myszy, aby ustawić czcionkę zastępczą. Kolejność wyświetlania: czcionka główna, czcionka zastępcza, czcionka Unicode.
|
||||
options.angelica.fontconfig.disable_custom_font=Wyłącz czcionkę niestandardową
|
||||
options.angelica.fontconfig.enable_custom_font=Włącz czcionkę niestandardową
|
||||
options.angelica.fontconfig.reset_values=Zresetuj wartości
|
||||
options.angelica.fontconfig.no_fonts_warning=Niestandardowe czicionki nie są dostępne na twoim systemie. Sprawdź logi, aby uzyskać więcej informacji.
|
||||
options.angelica.fontconfig.font_quality=Jakość czcionki: %s
|
||||
options.angelica.fontconfig.shadow_offset=Przesunięcie cienia: %s
|
||||
options.angelica.fontconfig.shadow_copies=Kopie cienia: %s
|
||||
options.angelica.fontconfig.bold_copies=Pogrubione kopie: %s
|
||||
options.angelica.fontconfig.glyph_scale=Skala znaków: %s
|
||||
options.angelica.fontconfig.glyph_aspect=Aspekt znaków: %s
|
||||
options.angelica.fontconfig.whitespace_scale=Rozmiar odstępu: %s
|
||||
options.angelica.fontconfig.glyph_spacing=Skalowanie znaków: %s
|
||||
options.angelica.fontconfig.font_aa_strength=Poziom wygładzania czcionki: %s
|
||||
options.angelica.fontconfig.aamode.aa_none=Wygładzanie czcionki: OFF
|
||||
options.angelica.fontconfig.aamode.aa_4x=Wygładzanie czcionki: 4x MSAA
|
||||
options.angelica.fontconfig.aamode.aa_16x=Wygładzanie czcionki: 16x MSAA
|
||||
options.angelica.fontconfig.font_brightness=Jasność czcionki: %s
|
||||
options.angelica.fontconfig.custom_font_scale=Niestandardowa skala czcionki: %s
|
||||
options.angelica.fontconfig.shadow_offset_unicode=Przesunięcie cienia Unicode: %s
|
||||
options.angelica.fontconfig.font_quality.tooltip=Jakość w której niestandardowe czcionki są renderowane. Zmniejsza rozpikselowanie kosztem wydajnośći i pamięci.
|
||||
options.angelica.fontconfig.shadow_offset.tooltip=Zasięg w którym cienie czcionek są renderowane. Również wpływa na renderowanie §lpogrubionego§r tekstu.
|
||||
options.angelica.fontconfig.shadow_copies.tooltip=Liczba cienie do renderowania za tekstem, każdy przesunięty aby uzyskać fałszywy efekt trójwymiaru.
|
||||
options.angelica.fontconfig.bold_copies.tooltip=Liczba kopii tekstu rysowanych podczas renderowania pogrubionego tekstu. Może zwiększyć jakość pogrubionego tekstu cieńszych czcionek.
|
||||
options.angelica.fontconfig.glyph_scale.tooltip=Skala każdego pojedynczego znaku. Ekstremalne wartości mogą sprawić, że gra będzie niegrywalna.
|
||||
options.angelica.fontconfig.glyph_aspect.tooltip=Proporcje każdego pojedynczego znaku.
|
||||
options.angelica.fontconfig.whitespace_scale.tooltip=Współczynnik skalowania znaków pustych, takich jak spacje.
|
||||
options.angelica.fontconfig.glyph_spacing.tooltip=Odstęp między poszczególnymi znakami.
|
||||
options.angelica.fontconfig.font_aa_strength.tooltip=Odstępy między próbkami stosowane w wygładzaniu czcionek. Wysokie wartości mogą powodować, że czcionki będą rozmyte, natomiast niskie wartości mogą powodować efekt aliasingu. Sprawdź, jakie ustawienia najlepiej sprawdzają się w Twoim przypadku.
|
||||
options.angelica.fontconfig.aamode.tooltip=Tryb wygładzania czcionek. Może znacznie poprawić wygląd i czytelność tekstu, który jest skalowany lub oglądany pod kątem, kosztem wydajności.
|
||||
options.angelica.fontconfig.font_brightness.tooltip=Wpływa na grubość czcionek z wygładzaniem krawędzi. Może poprawić czytelność niektórych tekstów. Wartość 0,5 pozostawia współczynnik alfa bez zmian. Skrajne wartości mogą sprawić, że tekst będzie wyglądał na postrzępiony.
|
||||
options.angelica.fontconfig.custom_font_scale.tooltip=Współczynnik skalowania, który wpływa na rozmiar niestandardowych czcionek. Należy go dostosować, jeśli tekst wyświetlany czcionką, którą wybrałeś, wydaje się zbyt mały lub zbyt duży w porównaniu z tekstem wyświetlanym bez użycia czcionek niestandardowych.
|
||||
options.angelica.fontconfig.shadow_offset_unicode.tooltip=Współczynnik przesunięcia cienia stosowany wyłącznie do czcionki Unicode. Ustaw tę wartość na 1,0x, jeśli na przykład korzystasz z paczki tekstur, która zastępuje fragmenty czcionki Unicode przypominające MC.
|
||||
options.angelica.hudCaching=Buforowanie HUD
|
||||
options.angelica.hudCaching.tooltip=Zapisuje HUD do bufora klatek i ponownie wykorzystuje go w kolejnych klatkach, aby zmniejszyć obciążenie związane z renderowaniem. Wymaga włączenia funkcji buforowania HUD podczas uruchamiania. [Semi-stabilne]
|
||||
options.angelica.droppedItemLimit=Limit renderowania upuszczonych przedmiotów
|
||||
options.angelica.droppedItemLimit.tooltip=Maksymalna liczba upuszczonych przedmiotów, które zostaną wyświetlone. Niższe wartości mogą poprawić wydajność.
|
||||
options.angelica.mobSpawnerRenderDistance=Odległość renderowania spawnera mobów
|
||||
options.angelica.mobSpawnerRenderDistance.tooltip=Odległość renderowania dla wirującego moba wewnątrz spawnera mobów. Ustaw na 0, aby wyłączyć renderowanie.
|
||||
options.angelica.shadowGraphAngleDelta=Próg odbudowy wykresu cienia.
|
||||
options.angelica.shadowGraphAngleDelta.tooltip=O ile ciało niebieskie może się przemieścić, zanim mapa cieni zostanie zaktualizowana. Należy pamiętać, że ta opcja jest wyłączona, gdy pakiet shaderów korzysta z funkcji wokselizacji.
|
||||
options.angelica.shadowGraphAngleDelta.always=Zawsze
|
||||
options.angelica.shadowGraphHorizonScale=Zmniejszenie horyzontu wykresu cieni
|
||||
options.angelica.shadowGraphHorizonScale.tooltip=Reguluje częstotliwość odświeżania mapy cieni, gdy ciało niebieskie znajduje się blisko horyzontu. Częstotliwość ma przebieg sinusoidalny.
|
||||
options.angelica.shadowGraphHorizonScale.off=Wył.
|
||||
options.angelica.itemdisplaylistcount=Limit pamięci podręcznej renderera elementów
|
||||
options.angelica.itemdisplaylistcount.tooltip=Górna granica liczby siatek przedmiotów, które mają być buforowane w zoptymalizowanym rendererze przedmiotów, obejmująca VBO oraz szablony przedmiotów upuszczanych i trzymanych. Wyższe wartości znacznie poprawią wydajność, ale mogą spowodować większe zużycie pamięci i pamięci VRAM.
|
||||
options.angelica.threadedChunkBuilding=Wielowątkowe budowanie chunków
|
||||
options.angelica.threadedChunkBuilding.tooltip=Jeśli włączone, tworzenie siatki chunków będzie odbywać się równolegle na wielu wątkach, co znacznie przyspieszy proces budowania chunków. Wyłącz tę opcję, jeśli wystąpią problemy z renderowaniem z modami wykorzystującymi niestandardowe renderery bloków. Wymaga ponownego załadowania renderera.
|
||||
options.angelica.parallelAtlasDecode=Równoległe dekodowanie tekstur
|
||||
options.angelica.parallelAtlasDecode.tooltip=Dekoduje tekstury bloków i obiektów na kilku wątkach. Szybsze uruchamianie i ponowne ładowanie zasobów. Wyłącz tę opcję, jeśli brakuje tekstur lub są one uszkodzone.
|
||||
options.angelica.parallelAtlasMipmaps=Równoległe mipmapy
|
||||
options.angelica.parallelAtlasMipmaps.tooltip=Generuje mipmapy atlasu tekstur na kilku wątkach. Szybsze uruchamianie i ponowne ładowanie zasobów. Wyłącz tę opcję, jeśli brakuje tekstur lub są one uszkodzone.
|
||||
options.angelica.batchedAtlasUpload=Zbiorcze przesyłanie atlasu
|
||||
options.angelica.batchedAtlasUpload.tooltip=Przesyłaj atlas tekstur dużymi partiami, a nie po jednej teksturze na raz.
|
||||
options.angelica.workerThreads=Wątki
|
||||
options.angelica.workerThreads.tooltip=Wątki wykorzystywane do ładowania tekstur podczas uruchamiania oraz ładowania pakietów shaderów. Domyślnie wybierana jest liczba oparta na liczbie rdzeni procesora.
|
||||
options.angelica.atlasPacks=Paczki ładujace tekstury...
|
||||
options.angelica.atlasPacks.tooltip=Wybiera które paczki zasobów mogą zostać odczytane na wielu wątkach podczas ładowania tekstur.
|
||||
options.angelica.atlasPacks.title=Ładowanie tekstur paczek zasobów
|
||||
options.angelica.atlasPacks.info=Zaznaczone paczki ładują tekstury równolegle. Zmiany zostaną zastosowane przy następnym odświeżeniu zasobów (F3+T).
|
||||
options.angelica.atlasPacks.builtin=wbudowany
|
||||
options.angelica.gpuCullingMode=Tryb chowania GPU
|
||||
options.angelica.gpuCullingMode.tooltip=Wybiera przebieg chowania chunków. Opcja „Tylko procesor” wykorzystuje emiter procesora. Opcja „Compute” wykonuje frustum culling na procesorze graficznym. Wymaga GL 4.3 / ARB_compute_shader + SSBO + Indirect Multi-Draw.
|
||||
options.angelica.gpuCullingMode.cpu_only=Tylko procesor
|
||||
options.angelica.gpuCullingMode.compute=Compute
|
||||
|
||||
options.angelica.overriddenBy=Nadpisane przez -D%s
|
||||
options.angelica.status.restart=Uruchom ponownie aby zastosować
|
||||
|
||||
options.angelica.renderer.page=Silnik renderowania
|
||||
options.angelica.renderer.link=Silnik renderowania
|
||||
options.angelica.renderer.link.tooltip=Backend graficzny którego używa gra. Kliknij aby otworzyć stronę silnika renderującego.
|
||||
options.angelica.renderer.backend=Backend
|
||||
options.angelica.renderer.backend.tooltip=API graficzne używane do renderowania gry. OpenGL jest domyślne. SDL GPU renderuje przez Vulkan, Metal lub Direct3D 12 i wraca do OpenGL jeśli żadne API nie może zostać uruchomione. Zmiana zacznie obowiązywać po ponownym uruchomieniu gry.
|
||||
options.angelica.renderer.backend.experimental=SDL GPU jest eksperymentalne: oczekuj crashy i błędów renderowania.
|
||||
options.angelica.renderer.backend.unavailable=SDL GPU jest niedostępne: %s
|
||||
options.angelica.renderer.backend.opengl=OpenGL
|
||||
options.angelica.renderer.backend.sdl_gpu=SDL GPU (Eksperymentalne)
|
||||
options.angelica.renderer.driver=Sterownik SDL GPU
|
||||
options.angelica.renderer.driver.tooltip=APU graficzne używane przez backend SDL GPU. Jeśli wybrany sterownik nie może zostać uruchomiony, SDL GPU spróbuje użyć swojego domyślnego, a później OpenGL. Ma Zmiana zacznie obowiązywać po ponownym uruchomieniu gry.
|
||||
options.angelica.renderer.driver.tooltip.macos=Automatyczny używa API Metal. Vulkan wymaga MoltenVK: włóż angelica-moltenvk.jar do folderu mods lub zainstaluj MoltenVK. Metal jest użyty jeśli Vulkan nie może zostać uruchomiony.
|
||||
options.angelica.renderer.driver.tooltip.windows=Automatyczny używa API Vulkan.
|
||||
options.angelica.renderer.driver.tooltip.d3d12=API Direct3D 12 jest bardzo eksperymentalne i prawdopobonie działa gorzej niżVulkan.
|
||||
options.angelica.renderer.driver.tooltip.other=Automatyczny używa API Vulkan.
|
||||
options.angelica.renderer.driver.auto=Automatyczny
|
||||
options.angelica.renderer.driver.vulkan=Vulkan
|
||||
options.angelica.renderer.driver.metal=Metal
|
||||
options.angelica.renderer.driver.d3d12=Direct3D 12
|
||||
options.angelica.renderer.glVersion=Wersja OpenGL
|
||||
options.angelica.renderer.glVersion.tooltip=Wersja kontekstu OpenGL do zażądania. Automatyczna używa najwyższej jaką wspiera twój sprzęt. Stosowana kiedy gra używa API OpenGL, również jeśli SDL GPU nie może zostać uruchomione. Zmiana zacznie obowiązywać po ponownym uruchomieniu gry.
|
||||
options.angelica.renderer.glVersion.auto=Automatyczna
|
||||
options.angelica.renderer.status=Stan
|
||||
options.angelica.renderer.status.tooltip=Czy działający backend jest zgodny z powyższymi ustawieniami.
|
||||
options.angelica.renderer.status.active=Aktywny
|
||||
options.angelica.renderer.status.forced=Wymuszony przez flagę JVM
|
||||
options.angelica.renderer.status.failed=SDL GPU nie mogło zostać uruchomione, sprawdź logi
|
||||
options.angelica.renderer.status.reset=Zresetowano do OpenGL po nieudanym starcie
|
||||
options.angelica.renderer.active=Używane API
|
||||
options.angelica.renderer.active.tooltip=Backend którego gra używa do renderowania.
|
||||
options.angelica.renderer.moltenvk=MoltenVK
|
||||
options.angelica.renderer.moltenvk.tooltip=Gdzie znaleziono MoltenVK. Sterownik SDL GPU Vulkan potrzebuje go na macOS.
|
||||
options.angelica.renderer.moltenvk.env=SDL_VULKAN_LIBRARY
|
||||
options.angelica.renderer.moltenvk.jar=angelica-moltenvk.jar
|
||||
options.angelica.renderer.moltenvk.system=Systemowa instalacja
|
||||
options.angelica.renderer.moltenvk.none=Nie znaleziony
|
||||
|
||||
options.angelica.tracy.page=Profiler Tracy
|
||||
options.angelica.tracy.enable=Włącz Tracy
|
||||
options.angelica.tracy.enable.tooltip=Włącz moduł profilujący Tracy. Wymaga obecności pliku angelica-tracy.jar w ścieżce klasy. Zmiana zacznie obowiązywać po ponownym uruchomieniu gry.
|
||||
options.angelica.tracy.allowRemote=Zezwól na zdalne połączenia
|
||||
options.angelica.tracy.allowRemote.tooltip=Zezwól na połączenia Tracy z innych komputerów, a nie tylko z localhost. Zmiana zacznie obowiązywać po ponownym uruchomieniu gry.
|
||||
options.angelica.tracy.fineZones=Dokładne strefy
|
||||
options.angelica.tracy.fineZones.tooltip=Włącza strefy Tracy dla znacznie większej liczby punktów połączeń. Więcej szczegółów, większe obciążenie. Zmiana wchodzi w życie po ponownym uruchomieniu gry.
|
||||
options.angelica.tracy.maxSrcLocs=Maksymalna liczba lokalizacji źródeł
|
||||
options.angelica.tracy.maxSrcLocs.tooltip=Maksymalna liczba różnych stref Tracy i punktów wywołania komunikatów, które mają zostać wstępnie przydzielone. Zmiana wchodzi w życie po ponownym uruchomieniu gry.
|
||||
options.angelica.tracy.status=Stan
|
||||
options.angelica.tracy.status.tooltip=Czy Tracy jest obecnie uruchomione i czy ustawienia są zgodne z powyższymi.
|
||||
options.angelica.tracy.status.running=Uruchomione
|
||||
options.angelica.tracy.status.runningForced=Uruchomione (wymuszone przez flagę JVM)
|
||||
options.angelica.tracy.status.failed=Nie udało się uruchomić, sprawdź log
|
||||
options.angelica.tracy.status.off=Wył.
|
||||
options.angelica.tracy.captureLength=Długość ujęcia
|
||||
options.angelica.tracy.captureLength.tooltip=Domyślna długość ujęcia Tracy rozpoczętego z tej strony lub /angelica tracy start. Ujęcia są zapisywane w katalogu angelica/profiles i otwierane za pomocą Tracy Profiler 0.14.
|
||||
options.angelica.tracy.captureAction=Ujęcie
|
||||
options.angelica.tracy.captureAction.tooltip=Uruchamia lub zatrzymuje rejestrację ujęcie w Tracy. Plik jest zapisywany w katalogu angelica/profiles i otwierany za pomocą Tracy Profiler 0.14.
|
||||
options.angelica.tracy.captureAction.start=Zacznij ujęcie
|
||||
options.angelica.tracy.captureAction.stop=Zatrzymaj ujęcie
|
||||
options.angelica.tracy.captureAction.saving=Zapisywanie...
|
||||
|
||||
pack.iris.select.title=Wybierz
|
||||
pack.iris.configure.title=Konfiguruj
|
||||
pack.iris.list.label=+ Przeciągnij i upuść paczki shaderów, aby je dodać
|
||||
pack.iris.list.label.lwjgl2=Zainstaluj lwjgl3ify aby móc przeciągać i upuszczać paczki shaderów
|
||||
label.iris.true=Wł.
|
||||
label.iris.false=Wył.
|
||||
|
||||
sodium.options.use_render_pass_optimization.name=Optymalizacja przebiegu renderowania
|
||||
sodium.options.use_render_pass_optimization.tooltip=Analizuje przezroczystość sprite'ów, aby automatycznie obniżać poziom złożoności operacji renderowania (np. z półprzezroczystego na wycięty, z wyciętego na nieprzezroczysty), gdy sprite nie wymaga bardziej zasobochłonnej operacji. Zmniejsza liczbę wywołań rysowania i nadmiernego rysowania.
|
||||
|
||||
options.angelica.aggressiveChunkLoading=Agresywne ładowanie chunków
|
||||
options.angelica.aggressiveChunkLoading.tooltip=Renderuje chunki natychmiast po załadowaniu, nawet jeśli sąsiednie chunki nie są jeszcze gotowe. Poprawia odczuwalną szybkość ładowania na granicach odległości renderowania. Chunki odbudowują się automatycznie po załadowaniu sąsiednich chunków. Przydatne w przypadku serwerów o małej odległości renderowania.
|
||||
angelica.debug.verbose.on=Szczegółowe F3: widoczne
|
||||
angelica.debug.verbose.off=Szczegółowe F3: ukryte
|
||||
angelica.debug.group.rendering=Renderowanie
|
||||
angelica.debug.group.position=Pozycja
|
||||
angelica.debug.group.looking_at_block=Oglądany blok
|
||||
angelica.debug.group.memory=Pamięć
|
||||
angelica.debug.group.system_specs=Specyfikacja systemu
|
||||
angelica.debug.group.forge=Forge
|
||||
angelica.debug.group.angelica=Angelica
|
||||
angelica.debug.group.shaders=Shadery
|
||||
angelica.debug.group.other=Inne
|
||||
angelica.debug.group.help=Pomoc
|
||||
angelica.debug.options.title=Opcje debugowania
|
||||
angelica.debug.options.warning=Te opcje służą wyłącznie do celów testowych. Mogą one spowolnić działanie komputera, spowodować awarię gry lub zjeść twojego kamiennego zwierzaka.
|
||||
angelica.debug.options.search=Wyszukaj...
|
||||
angelica.debug.options.category.text=Tekst na ekranie debugowania
|
||||
angelica.debug.options.category.renderer=Renderery debugowania
|
||||
angelica.debug.options.category.layout=Układ
|
||||
angelica.debug.options.profile.default=Domyślny profil
|
||||
angelica.debug.options.profile.performance=Profil wydajności
|
||||
angelica.debug.options.failed.tooltip=Ten wpis spowodował błąd i jest wyłączony do czasu ponownego uruchomienia. Sprawdź dziennik.
|
||||
angelica.debug.options.flat_notice=Klasyczny układ F3 jest włączony (ekran zgrupowany jest wyłączony lub renderer czcionek Angelica jest wyłączony): obowiązują wyłącznie te wpisy.
|
||||
angelica.debug.options.text_size=Rozmiar tekstu F3
|
||||
angelica.debug.options.text_size.match=Dopasuj do GUI
|
||||
angelica.debug.options.text_size.auto=Automatycznie
|
||||
angelica.debug.options.text_size.tooltip=Dopasuj do GUI: taki sam rozmiar jak skala interfejsu użytkownika. Automatycznie: połowa największego rozmiaru, który mieści się w oknie. Nigdy większy niż skala GUI.
|
||||
angelica.debug.options.category.panels=Panele
|
||||
angelica.debug.options.panels.tooltip=Użyj grup=%s w regułach linii F3 (konfiguracja Angelica compat), aby przenieść linię do tego panelu.
|
||||
angelica.debug.options.status.overlay=W nakładce
|
||||
angelica.debug.options.status.always=Zawsze
|
||||
angelica.debug.entry.minecraft.game_version=Wersja gry
|
||||
angelica.debug.entry.minecraft.fps=FPS
|
||||
angelica.debug.entry.minecraft.tps=Czas ticku serwera
|
||||
angelica.debug.entry.minecraft.player_position=Pozycja gracza
|
||||
angelica.debug.entry.minecraft.player_section_position=Wybierz pozycję
|
||||
angelica.debug.entry.minecraft.player_speed=Prędkość gracza
|
||||
angelica.debug.entry.minecraft.biome=Biom
|
||||
angelica.debug.entry.minecraft.light_levels=Poziomy światła
|
||||
angelica.debug.entry.minecraft.heightmap=Mapa wysokości
|
||||
angelica.debug.entry.minecraft.chunk_render_stats=Statystyki renderowania chunków
|
||||
angelica.debug.entry.minecraft.entity_render_stats=Statystyki renderowania bytów
|
||||
angelica.debug.entry.minecraft.particle_render_stats=Statystyki cząstek
|
||||
angelica.debug.entry.minecraft.chunk_source_stats=Statystyki pamięci podręcznej chunków
|
||||
angelica.debug.entry.minecraft.post_effect=Efekty postprocesu
|
||||
angelica.debug.entry.minecraft.memory=Pamięć
|
||||
angelica.debug.entry.minecraft.system_specs=Specyfikacja systemu
|
||||
angelica.debug.entry.minecraft.looking_at_block=Oglądany blok
|
||||
angelica.debug.entry.forge.brandings=Wersje Forge i modów
|
||||
angelica.debug.entry.angelica.renderer=Renderer Angelica
|
||||
angelica.debug.entry.angelica.renderer_verbose=Szczegółowe informacje o rendererze
|
||||
angelica.debug.entry.angelica.shaders=Paczka shaderów
|
||||
angelica.debug.entry.angelica.dynamic_lights=Dynamiczne światła
|
||||
angelica.debug.entry.angelica.fog=Mgła
|
||||
angelica.debug.entry.angelica.animations=Animacje tekstur
|
||||
angelica.debug.entry.angelica.other_left=Inne mody (lewo)
|
||||
angelica.debug.entry.angelica.other_right=Inne mody (prawo)
|
||||
angelica.debug.entry.angelica.help=Pomoc dotycząca klawiszy
|
||||
angelica.debug.entry.minecraft.3d_crosshair=Celownik 3D
|
||||
angelica.debug.entry.minecraft.entity_hitboxes=Hitboxy bytów
|
||||
angelica.debug.entry.minecraft.chunk_borders=Granice chunków
|
||||
angelica.debug.entry.minecraft.chunk_section_visibility=Widoczność sekcji
|
||||
angelica.debug.entry.minecraft.visualize_block_light_levels=Poziomy światła bloków
|
||||
angelica.debug.entry.minecraft.visualize_sky_light_levels=Poziomy światła nieba
|
||||
angelica.debug.entry.minecraft.visualize_heightmap=Znaczniki mapy wysokości
|
||||
angelica.debug.entry.minecraft.visualize_collision_boxes=Prostokąty kolizji
|
||||
angelica.debug.entry.minecraft.visualize_solid_faces=Powierzchnie pełne
|
||||
angelica.debug.entry.minecraft.visualize_water_levels=Poziomy wody
|
||||
angelica.debug.entry.minecraft.visualize_entity_supporting_block=Blok wspierający byt
|
||||
angelica.debug.entry.angelica.chunk_minimap=Minimapa chunków
|
||||
angelica.debug.entry.minecraft.game_version.tooltip=Wiersz z wersją Minecrafta u góry ekranu debugowania
|
||||
angelica.debug.entry.minecraft.fps.tooltip=Liczba klatek na sekundę, aktualne ograniczenie liczby klatek oraz aktualizacje chunków – wyświetlane w lewym górnym rogu
|
||||
angelica.debug.entry.minecraft.tps.tooltip=Ticki zintegrowanego serwera w milisekundach; tylko w trybie jednoosobowym
|
||||
angelica.debug.entry.minecraft.player_position.tooltip=Twoja dokładna pozycja, blok, chunk, kierunek orientacji i wymiar
|
||||
angelica.debug.entry.minecraft.player_section_position.tooltip=Twoja pozycja w bloku względem sekcji 16x16x16, w której się znajdujesz
|
||||
angelica.debug.entry.minecraft.player_speed.tooltip=Twoja prędkość w blokach na tick, prędkość chodzenia i latania oraz to, czy znajdujesz się na ziemi
|
||||
angelica.debug.entry.minecraft.biome.tooltip=Biom w miejscu, w którym się znajdujesz, wraz z modem z którego jest
|
||||
angelica.debug.entry.minecraft.light_levels.tooltip=Poziom światła w Twojej pozycji, w podziale na światło nieba i światło bloku
|
||||
angelica.debug.entry.minecraft.heightmap.tooltip=Wysokość górnego bloku w Twojej pozycji oraz górnej wypełnionej części Twojego chunku
|
||||
angelica.debug.entry.minecraft.chunk_render_stats.tooltip=Widoczne i całkowite sekcje renderowania, zasięg renderowania oraz liczba animowanych sprite’ów
|
||||
angelica.debug.entry.minecraft.entity_render_stats.tooltip=Ile obiektów zostało wyrenderowanych, ukrytych lub odrzuconych w tej klatce spośród tych, które zostały załadowane
|
||||
angelica.debug.entry.minecraft.particle_render_stats.tooltip=Liczba cząstek z menedżera cząstek Vanilli.
|
||||
angelica.debug.entry.minecraft.chunk_source_stats.tooltip=Statystyki dotyczące pamięci podręcznej chunków klienta z dostawcy świata
|
||||
angelica.debug.entry.minecraft.post_effect.tooltip=Nazwa aktywnej grupy post-procesowej Vanilli
|
||||
angelica.debug.entry.minecraft.memory.tooltip=Wykorzystanie sterty Javy, przydzielona sterta, tempo alokacji oraz pamięć bufora bezpośredniego
|
||||
angelica.debug.entry.minecraft.system_specs.tooltip=Wersja Javy, liczba rdzeni procesora, rozdzielczość ekranu, układ graficzny i wersja OpenGL
|
||||
angelica.debug.entry.minecraft.looking_at_block.tooltip=Współrzędne, nazwa w rejestrze i metadane bloku znajdującego się pod celownikiem
|
||||
angelica.debug.entry.forge.brandings.tooltip=MCP, Wersje FML i Forge oraz liczba załadowanych i aktywnych modów
|
||||
angelica.debug.entry.angelica.renderer.tooltip=Wersja Angelica, backend renderowania i rozmiary bufora terenu
|
||||
angelica.debug.entry.angelica.renderer_verbose.tooltip=Adds detailed renderer timings to the Angelica panel, like F3+V
|
||||
angelica.debug.entry.angelica.shaders.tooltip=Aktywna paczka shaderów i szczegóły dotyczące jego potoku renderowania; pokazuje off kiedy shadery są wyłączone
|
||||
angelica.debug.entry.angelica.dynamic_lights.tooltip=Liczba dynamicznych źródeł światła, aktualizacji w tej klatce i oczekujące przebudowy chunków
|
||||
angelica.debug.entry.angelica.fog.tooltip=Stan mgły: włączony lub wyłączony, tryb, odległość początkowa i końcowa, gęstość oraz kolor
|
||||
angelica.debug.entry.angelica.animations.tooltip=Tryb animacji tekstur; ukryty, chyba że włączono przyspieszenie animacji
|
||||
angelica.debug.entry.angelica.other_left.tooltip=Wiersze dodawane przez inne modyfikacje po lewej stronie ekranu debugowania
|
||||
angelica.debug.entry.angelica.other_right.tooltip=Wiersze dodawane przez inne modyfikacje po prawej stronie ekranu debugowania
|
||||
angelica.debug.entry.angelica.help.tooltip=Przypomnienie skrótów klawiszowych F3, wyświetlane przy otwartym ekranie debugowania
|
||||
angelica.debug.entry.minecraft.3d_crosshair.tooltip=Zastępuje celownik kolorowymi liniami osi X, Y i Z, które dostosowują się do kierunku patrzenia
|
||||
angelica.debug.entry.minecraft.entity_hitboxes.tooltip=Pokazuje hitboxy bytów, tak jak F3+B
|
||||
angelica.debug.entry.minecraft.chunk_borders.tooltip=Wskazuje chunk i sekcję, w której się znajdujesz, tak jak F3+G
|
||||
angelica.debug.entry.minecraft.chunk_section_visibility.tooltip=Oznacza pobliskie sekcje, których ściany nie stykają się ze sobą, co powoduje schowanie terenu znajdującego się za nimi
|
||||
angelica.debug.entry.minecraft.visualize_block_light_levels.tooltip=Rysuje wartość jasności na każdym pobliskim bloku, który nie jest całkowicie ciemny
|
||||
angelica.debug.entry.minecraft.visualize_sky_light_levels.tooltip=Rysuje wartość światła nieba na każdym pobliskim bloku, który nie ma pełnego oświetlenia nieba
|
||||
angelica.debug.entry.minecraft.visualize_heightmap.tooltip=Oznacza górny blok każdej kolumny w sąsiednich chunkach
|
||||
angelica.debug.entry.minecraft.visualize_collision_boxes.tooltip=Wskazuje zderzające się obiekty w promieniu kilku bloków od Ciebie
|
||||
angelica.debug.entry.minecraft.visualize_solid_faces.tooltip=Barwi powierzchnie bloków pełnych, które są widoczne dla sąsiedniego, nieprzezroczystego bloku znajdującego się w pobliżu
|
||||
angelica.debug.entry.minecraft.visualize_water_levels.tooltip=Pokazuje pobliskie zbiorniki wodne wraz z ich poziomem przepływu w skali od 1 do 8
|
||||
angelica.debug.entry.minecraft.visualize_entity_supporting_block.tooltip=Pokazuje kontury bloków, na których stoją poszczególne pobliskie byty; twój jest czerwony, a pozostałe zielone
|
||||
angelica.debug.entry.angelica.chunk_minimap.tooltip=Mała mapa stanów renderowania chunków w rogu
|
||||
angelica.debug.chunk_borders.on=Granice chunków: Widoczne
|
||||
angelica.debug.chunk_borders.off=Granice chunków: ukryte
|
||||
angelica.debug.help.options=F3 + F6 = Edytuj opcje debugowania
|
||||
angelica.debug.help.hitboxes=F3 + B = Pokaż hitboxy
|
||||
angelica.debug.help.chunk_borders=F3 + G = Pokaż granice chunków
|
||||
angelica.debug.help.verbose=F3 + V = Szczegółowe informacje o rendererze
|
||||
angelica.debug.help.graphs=Alt + F3 = Wykresy FPS i TPS
|
||||
options.angelica.texteffects.rgb_colors=Kolory RGB
|
||||
options.angelica.texteffects.rgb_colors.tooltip=Włącz kolory szesnastkowe za pomocą składni %s
|
||||
options.angelica.texteffects.gradients=Gradienty
|
||||
options.angelica.texteffects.gradients.tooltip=Płynne przejścia kolorów przez
|
||||
options.angelica.texteffects.rainbow=Tęcza
|
||||
options.angelica.texteffects.rainbow.tooltip=Cykliczne kolory tęczy przez
|
||||
options.angelica.texteffects.wave_text=Tekst falowy
|
||||
options.angelica.texteffects.wave_text.tooltip=Animowany, podskakujący tekst przez
|
||||
options.angelica.texteffects.wave_amplitude=Amplituda
|
||||
options.angelica.texteffects.wave_amplitude.tooltip=Odległość, na jaką odbija się tekst falisty (domyślnie 2,0)
|
||||
options.angelica.texteffects.dinnerbone_text=Tekst Dinnerbone
|
||||
options.angelica.texteffects.dinnerbone_text.tooltip=Tekst odwrócony do góry nogami przez
|
||||
options.angelica.texteffects.drop_shadow=Rzucany cień
|
||||
options.angelica.texteffects.drop_shadow.tooltip=Cień rzucany dla poszczególnych segmentów za pomocą %s, cień kolorowy za pomocą
|
||||
options.angelica.texteffects.ampersand_conversion=Konwersja znaku „&”
|
||||
options.angelica.texteffects.ampersand_conversion.tooltip=Konwertuj kody prefiksowe %s na kody formatujące w momencie renderowania
|
||||
|
||||
sodium.options.multidraw_mode.name=Tryb Multi-Draw
|
||||
sodium.options.multidraw_mode.tooltip=Określa sposób wysyłania poleceń rysowania podczas renderowania terenu. §lDirect§r używa glMultiDrawElementsBaseVertex (domyślna, GL 3.2+). §lIndirect§r renderowanie pośrednie za pomocą procesora graficznego (GL 4.3+), co może zmniejszyć obciążenie procesora na niektórych urządzeniach, i jest wymagane do chowania GPU / Hi-Z. §lIndywidualny§r wysyła pojedyncze wywołania rysowania.
|
||||
sodium.options.multidraw_mode.direct=Bezpośredni
|
||||
sodium.options.multidraw_mode.indirect=Pośredni
|
||||
sodium.options.multidraw_mode.individual=Indywidualny
|
||||
sodium.options.upload_method.name=Metoda przesyłania strumieniowego
|
||||
sodium.options.upload_method.tooltip=Zmienia sposób przesyłania danych do procesora graficznego. Niektóre opcje mogą poprawić wydajność w przypadku niektórych sterowników.
|
||||
sodium.options.upload_method.buffer_data=BufferData
|
||||
sodium.options.upload_method.buffer_subdata=BufferSubData
|
||||
sodium.options.upload_method.map_buffer=MapBufferRange
|
||||
sodium.options.enable_deferred_batching.name=Dozowanie cząstek
|
||||
sodium.options.enable_deferred_batching.tooltip=Renderuje cząstki jako instancjonowane czworokąty lub, jeśli instancjonowanie nie jest dostępne, jako połączone obiekty. Wyłącz tę opcję, jeśli cząstki są renderowane nieprawidłowo.
|
||||
Loaded 100 of 114 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user