Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions src/main/java/net/vulkanmodnext/VulkanBridge.java
Original file line number Diff line number Diff line change
Expand Up @@ -420,8 +420,7 @@ boolean renderTerrainLayer(int layerOrdinal, int[] chunks, int chunkCount, float
*
* {@code alphaCutoff} is the game's own alpha test for this batch, and the
* two values differ: particles cut at one 255th, weather at a tenth.
*/
/**
*
* @return false when the renderer could not take the batch, so that the
* caller draws it the way the game would have. It used to return
* nothing, drop what would not fit and leave the caller believing
Expand Down
22 changes: 11 additions & 11 deletions src/main/java/net/vulkanmodnext/VulkanLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public final class VulkanLoader {
/**
* Volatile so that reading it needs no monitor.
*
* It is written once, inside the synchronized constructor below, and read
* It is written once, inside the synchronized bridge() below, and read
* from the render thread several times a frame and from chunk-building
* threads besides. A monitor was being taken for each of those reads, on a
* field that never changes again after the one write; volatile gives the
Expand Down Expand Up @@ -107,7 +107,7 @@ private static int maxSupportedJava() {
*
* LWJGL's own default of 64 KiB holds 252 entries, and current drivers list
* around 270 for a single card — machines have failed to start over a
* shortfall of three kilobytes. 2 MiB is 8064 entries, twenty-nine drivers'
* shortfall of three kilobytes. 2 MiB is 8065 entries, twenty-nine drivers'
* worth, which is room for whatever a multi-GPU machine with overlay layers
* turns out to list.
*
Expand Down Expand Up @@ -158,6 +158,15 @@ public static synchronized VulkanBridge bridge() {
if (Boolean.getBoolean("vulkanmodnext.forceFallback")) {
throw new VulkanUnavailableException("Vulkan renderer switched off by vulkanmodnext.forceFallback");
}
String platform = net.vulkanmodnext.core.Platform.unsupportedPlatform();
if (platform != null) {
throw new VulkanUnavailableException("This mod ships its Vulkan half with native"
+ " libraries for 64-bit x86 desktops only, and this machine is " + platform + ". Nothing"
+ " here can load, so the game renders on OpenGL as it always did."
+ " Phones and tablets running Minecraft Java through a translation layer"
+ " land here: the world is drawn by that layer, and everything this mod"
+ " does besides the renderer still works.");
}
int java = javaFeatureVersion();
int ceiling = maxSupportedJava();
if (java > ceiling) {
Expand All @@ -169,15 +178,6 @@ public static synchronized VulkanBridge bridge() {
+ " -Dvulkanmodnext.javaCeiling=" + java + " — what decides this is the JNI version"
+ " rather than the Java one, and it moves far more rarely.");
}
String arch = net.vulkanmodnext.core.Platform.unsupportedArchitecture();
if (arch != null) {
throw new VulkanUnavailableException("This mod ships its Vulkan half with native"
+ " libraries for 64-bit x86 only, and this machine is " + arch + ". Nothing"
+ " here can load, so the game renders on OpenGL as it always did."
+ " Phones and tablets running Minecraft Java through a translation layer"
+ " land here: the world is drawn by that layer, and everything this mod"
+ " does besides the renderer still works.");
}
reserveStackSpace();
try {
URLClassLoader loader = new IsolatingLoader(collectUrls(), VulkanLoader.class.getClassLoader());
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/net/vulkanmodnext/VulkanModNext.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
* loader of its own with a different LWJGL, and it is brought up later, from
* the game's own client side, once there is a window to draw into.
*
* @see net.vulkanmodnext.client.VulkanLoader for how the other half is reached
* @see net.vulkanmodnext.VulkanLoader for how the other half is reached
* @see net.vulkanmodnext.VulkanBridge for the whole of what the two halves share
*/
@Mod(
Expand Down
33 changes: 20 additions & 13 deletions src/main/java/net/vulkanmodnext/client/AnimatedSprites.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,19 @@
* patch that records them is disabled: the failure is "no saving", never "the
* world stopped moving".
*
* A sprite that no chunk has ever reported is always updated. That is what
* keeps the item in your hand, the fire on a burning creature and the texture
* in a menu moving — none of them are terrain, so none of them are in any
* chunk's record, and a rule that only knew about terrain would freeze them.
* Fluids are always updated for the same reason and one more: they are what a
* player looks at when they want to know whether the game is still running.
* Anything drawn that is not terrain is vouched for another way. An item
* model marks its sprites as it is drawn — in the hand, in a menu, in a frame
* or on the ground — and they stay marked for a second or two. Water, lava,
* fire and portals are always updated: fluids are drawn by a renderer that
* never asks a model, fire burns on creatures as well as on blocks, and they
* are what a player looks at when they want to know whether the game is still
* running.
*
* <p>What remains, and it is why this ships switched off: a texture that is
* both a block and an item freezes in your hand while no such block is in
* sight. Holding a block of magma in a world without one placed is the shape
* of it.
* <p>What remains, and it is why this ships switched off: an animated block
* texture drawn by something that is neither a chunk nor an item model — a
* block entity renderer, a creature, a mod's own geometry — freezes while no
* chunk in sight uses it. There is no "never reported by a chunk" rule behind
* this; a sprite is updated only for the reasons above.
*/
public final class AnimatedSprites {

Expand All @@ -69,8 +71,8 @@ public final class AnimatedSprites {
private static int words;

/**
* Sprites that must be updated whatever is on screen: the ones no chunk
* has ever used, and the fluids.
* Sprites that must be updated whatever is on screen: water, lava, fire
* and portals, matched by name.
*/
private static long[] always = new long[0];

Expand Down Expand Up @@ -188,7 +190,12 @@ public static synchronized void index(List<TextureAtlasSprite> animated) {
itemsBefore = new long[words];
gathering = new long[words];
wanted = new long[words];
BY_STATE.clear();
// Under its own lock: build threads read and fill this map while the
// tick that reindexes runs, and they only ever hold that lock, not
// this method's.
synchronized (BY_STATE) {
BY_STATE.clear();
}
synchronized (BY_ITEM_MODEL) {
BY_ITEM_MODEL.clear();
}
Expand Down
10 changes: 9 additions & 1 deletion src/main/java/net/vulkanmodnext/client/BackgroundThrottle.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,18 @@ private BackgroundThrottle() {
*/
private static boolean published;

/**
* Off on Android. There the "window" is a surface the launcher's shim
* reports focus for, and a focus that reads as lost while the game is on
* screen would hold it at the background cap for the whole session. The
* system already stops drawing an app that is not in front.
*/
private static final boolean ANDROID = net.vulkanmodnext.core.Platform.android();

/** Called at the end of every rendered frame. */
public static void afterFrame() {
int limit = VulkanConfig.getBackgroundFpsLimit();
boolean throttling = limit > 0 && !Display.isActive();
boolean throttling = limit > 0 && !ANDROID && !Display.isActive();
if (throttling != published) {
published = throttling;
System.setProperty("vulkanmodnext.frameThrottled", Boolean.toString(throttling));
Expand Down
22 changes: 19 additions & 3 deletions src/main/java/net/vulkanmodnext/client/BlockLightSources.java
Original file line number Diff line number Diff line change
Expand Up @@ -140,10 +140,13 @@ public static void update(double viewX, double viewY, double viewZ, int radius)
shift(viewX, viewY, viewZ);
long now = System.nanoTime();
if (!sweeping) {
// Measured from where the last sweep started, not from lastX: shift()
// above has just moved lastX to this frame's camera, so comparing
// against it measured one frame's motion and never fired.
boolean moved = Double.isNaN(lastX)
|| Math.abs(viewX - lastX) > MOVED
|| Math.abs(viewY - lastY) > MOVED
|| Math.abs(viewZ - lastZ) > MOVED;
|| Math.abs(viewX - buildX) > MOVED
|| Math.abs(viewY - buildY) > MOVED
|| Math.abs(viewZ - buildZ) > MOVED;
if (!moved && now - lastScanNanos < INTERVAL_NANOS) {
return;
}
Expand All @@ -164,6 +167,19 @@ public static void update(double viewX, double viewY, double viewZ, int radius)
stepSweep(viewX, viewY, viewZ);
}

/**
* Drops everything found in the world being left.
*
* The list is positions relative to the camera, so after a change of
* dimension it would light the new world with the old one's torches until
* the next sweep finished.
*/
public static void forget() {
count = 0;
sweeping = false;
lastX = Double.NaN;
}

/**
* Moves the stored positions to follow the camera between sweeps.
*
Expand Down
6 changes: 3 additions & 3 deletions src/main/java/net/vulkanmodnext/client/ChunkBuildStats.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@
* side of every block, and reported 24 ns a block. Two calls to
* {@code System.nanoTime} cost about that between them on their own: the
* measurement was most of what it measured, and no conclusion could be drawn
* about the work. A whole chunk is forty thousand block positions, so one
* clock pair around the rebuild is four orders of magnitude cheaper per block
* and reports the same total.
* about the work. A chunk section is four thousand block positions (16 cubed),
* so one clock pair around the rebuild is three orders of magnitude cheaper
* per block and reports the same total.
*
* That also makes the number the useful one. Nobody wants to know what a
* hashmap lookup costs; the question is whether a chunk takes longer to build
Expand Down
27 changes: 23 additions & 4 deletions src/main/java/net/vulkanmodnext/client/Diagnostics.java
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ private static void mirror(org.apache.logging.log4j.core.LogEvent event) {
return;
}
StringBuilder line = new StringBuilder()
.append('[').append(STAMP.format(new Date())).append("] ")
.append('[').append(stamp()).append("] ")
.append(event.getLevel()).append(' ')
.append(name).append(": ")
.append(event.getMessage().getFormattedMessage());
Expand Down Expand Up @@ -250,14 +250,33 @@ public static void flushNow(String reason) {
try {
PrintWriter out = open();
if (out != null) {
out.println("[" + STAMP.format(new Date()) + "] EVENT: " + reason);
out.flush();
// Under the lock, and after any folded run is written out:
// mirrored lines arrive from other threads, and an event line
// written between a run and its "repeated" summary reads as
// though it happened before the lines it followed.
synchronized (LOCK) {
flushRepeat();
out.println("[" + stamp() + "] EVENT: " + reason);
out.flush();
}
}
} catch (Throwable ignored) {
// Diagnostics must never be the reason something breaks.
}
}

/**
* The time of day for a line. {@code SimpleDateFormat} keeps its working
* state in the instance, and mirrored log lines are formatted on whichever
* thread logged them — chunk builders included — so it is never used
* unguarded.
*/
private static String stamp() {
synchronized (STAMP) {
return STAMP.format(new Date());
}
}

private static PrintWriter open() throws IOException {
if (writer != null) {
return writer;
Expand Down Expand Up @@ -516,7 +535,7 @@ private static void emitSnapshot(PrintWriter out) {

private static void writeSnapshot(PrintWriter out) {
Minecraft mc = Minecraft.getMinecraft();
out.println("[" + STAMP.format(new Date()) + "] snapshot");
out.println("[" + stamp() + "] snapshot");
out.println(" fps: " + Minecraft.getDebugFPS()
+ ", world: " + (mc.world == null ? "none" : "loaded")
+ ", gui: " + (mc.currentScreen == null ? "none" : mc.currentScreen.getClass().getSimpleName()));
Expand Down
26 changes: 24 additions & 2 deletions src/main/java/net/vulkanmodnext/client/EntityCapture.java
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,16 @@ public final class EntityCapture {
/** The frame of the creature being drawn, and one frame per skeleton level. */
private static final float[][] STACK = new float[MAX_DEPTH][16];
private static int depth;
/**
* Whether each {@link #beginPart} still waiting for its {@link #endPart}
* pushed a frame, one entry per nested call.
*
* A part past the depth limit, or one that threw before its push, pushes
* nothing — and its end used to pop the parent's frame regardless, so every
* sibling after it was placed one level too shallow.
*/
private static final boolean[] PUSHED = new boolean[64];
private static int calls;
private static final float[] LOCAL = new float[16];
private static final float[] COMPOSED = new float[16];

Expand Down Expand Up @@ -195,6 +205,7 @@ public static void disarm() {
}
armed = false;
depth = 0;
calls = 0;
frameParts = 0;
frameQuads = 0;
frameTextures = 0;
Expand Down Expand Up @@ -222,6 +233,7 @@ public static void beginModel() {
long start = System.nanoTime();
try {
depth = 0;
calls = 0;
readMatrix(STACK[0]);
matrixReads++;
} catch (Throwable ignored) {
Expand All @@ -242,6 +254,7 @@ public static void beginPart(ModelRenderer part, float scale) {
if (!armed || part == null) {
return;
}
int before = depth;
try {
if (depth >= MAX_DEPTH - 1) {
return;
Expand Down Expand Up @@ -291,6 +304,11 @@ public static void beginPart(ModelRenderer part, float scale) {
}
} catch (Throwable ignored) {
// As above.
} finally {
if (calls < PUSHED.length) {
PUSHED[calls] = depth != before;
}
calls++;
}
}

Expand Down Expand Up @@ -330,7 +348,11 @@ private static void noteTexture() {

/** Leaves the part's frame, so its siblings are placed beside it and not inside it. */
public static void endPart() {
if (armed && depth > 0) {
if (!armed || calls == 0) {
return;
}
calls--;
if (calls < PUSHED.length && PUSHED[calls] && depth > 0) {
depth--;
}
}
Expand Down Expand Up @@ -424,7 +446,7 @@ static void localTransform(ModelRenderer part, float scale, float[] out) {
*
* Every matrix in this chain is a rotation and a shift, so the fourth row
* is known and the fourth column of the product is the only one that needs
* the shift added. Twenty-seven multiplications instead of sixty-four, on
* the shift added. Thirty-six multiplications instead of sixty-four, on
* the one piece of arithmetic that runs once per bone of every creature on
* screen.
*/
Expand Down
24 changes: 13 additions & 11 deletions src/main/java/net/vulkanmodnext/client/Flight.java
Original file line number Diff line number Diff line change
Expand Up @@ -394,14 +394,6 @@ private static void enterWorld(Minecraft mc) {
ticks = 0;
}

/**
* Loads a named preset, if one was asked for.
*
* Applied after the world is there rather than at startup, because two of
* these presets read the machine they are on — how many cores there are to
* build chunks with, how much memory the card admits to — and one of them
* moves the game's own video settings.
*/
/**
* Resizes the window, if one was asked for, before anything is measured.
*
Expand Down Expand Up @@ -439,6 +431,14 @@ private static void applyWindow(Minecraft mc) {
}
}

/**
* Loads a named preset, if one was asked for.
*
* Applied after the world is there rather than at startup, because two of
* these presets read the machine they are on — how many cores there are to
* build chunks with, how much memory the card admits to — and one of them
* moves the game's own video settings.
*/
private static void applyPreset(Minecraft mc) {
if (PRESET.isEmpty()) {
return;
Expand Down Expand Up @@ -617,9 +617,11 @@ public static void onFrameEnd() {
}
String name = pendingShot;
// The same frame again on the very next one, when asked. A camera
// turning at eighteen degrees a second moves a twentieth of a degree
// between two frames, so a pair that differs by more than a rounding
// error says the picture being read is not the picture just drawn —
// turning at twelve degrees a second — one turn over the default
// thirty-second spin — moves a twentieth of a degree between two
// frames at a couple of hundred a second, so a pair that differs by
// more than a rounding error says the picture being read is not the
// picture just drawn —
// which is the one thing that cannot be told apart from a fault in the
// renderer by looking at a single frame.
if (PAIRS && !name.endsWith("b")) {
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/net/vulkanmodnext/client/GuiVulkanPatches.java
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,8 @@ public void drawScreen(int mouseX, int mouseY, float partialTicks) {
}

/**
* The three lines under the list: what the selected group is, what turning
* it off costs, and what it said if it failed.
* The lines under the list, two at most: what turning the selected group
* off costs, or what it said if it failed.
*/
private void drawFoot(int bottom) {
if (this.selected == null) {
Expand Down
Loading
Loading