Skip to content

Commit

Permalink
fix: Workaround Intel GPU issues in multi-draw renderer
Browse files Browse the repository at this point in the history
Should have minimal performance impact, if any. It seems Intel's
drivers on Windows can't handle accessing indirect commands from
a buffer and needs the command array passed as a pointer into
client memory.

Of course, AMD requires the opposite, because the state of OpenGL
drivers on Windows is nothing but sadness.
  • Loading branch information
jellysquid3 committed Mar 15, 2021
1 parent de545a5 commit 34eb1d2
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import it.unimi.dsi.fastutil.objects.ObjectArrayFIFOQueue;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import me.jellysquid.mods.sodium.client.SodiumClientMod;
import me.jellysquid.mods.sodium.client.gl.arena.GlBufferArena;
import me.jellysquid.mods.sodium.client.gl.arena.GlBufferRegion;
import me.jellysquid.mods.sodium.client.gl.array.GlVertexArray;
Expand All @@ -26,6 +25,7 @@
import me.jellysquid.mods.sodium.client.render.chunk.passes.BlockRenderPass;
import me.jellysquid.mods.sodium.client.render.chunk.region.ChunkRegion;
import me.jellysquid.mods.sodium.client.render.chunk.region.ChunkRegionManager;
import net.minecraft.util.Formatting;
import net.minecraft.util.Util;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
Expand Down Expand Up @@ -87,7 +87,7 @@ public class GL43ChunkRenderBackend extends ChunkRenderBackendMultiDraw<LCBGraph
private final GlMutableBuffer commandBuffer;

private final ChunkDrawParamsVector uniformBufferBuilder;
private final IndirectCommandBufferVector commandBufferBuilder;
private final IndirectCommandBufferVector commandClientBufferBuilder;

private final MemoryTracker memoryTracker = new MemoryTracker();

Expand All @@ -96,11 +96,12 @@ public GL43ChunkRenderBackend(ChunkVertexType vertexType) {

this.bufferManager = new ChunkRegionManager(this.memoryTracker);
this.uploadBuffer = new GlMutableBuffer(GL15.GL_STREAM_COPY);
this.uniformBuffer = new GlMutableBuffer(GL15.GL_STATIC_DRAW);
this.commandBuffer = new GlMutableBuffer(GL15.GL_STATIC_DRAW);

this.uniformBuffer = new GlMutableBuffer(GL15.GL_STATIC_DRAW);
this.uniformBufferBuilder = ChunkDrawParamsVector.create(2048);
this.commandBufferBuilder = IndirectCommandBufferVector.create(2048);

this.commandBuffer = isWindowsIntelDriver() ? null : new GlMutableBuffer(GL15.GL_STATIC_DRAW);
this.commandClientBufferBuilder = IndirectCommandBufferVector.create(2048);
}

@Override
Expand Down Expand Up @@ -166,11 +167,16 @@ public void render(ChunkRenderListIterator renders, ChunkCameraContext camera) {
this.bufferManager.cleanup();

this.setupDrawBatches(renders, camera);
this.setupCommandBuffers();
this.buildCommandBuffer();

if (this.commandBuffer != null) {
this.commandBuffer.bind(GL40.GL_DRAW_INDIRECT_BUFFER);
this.commandBuffer.upload(GL40.GL_DRAW_INDIRECT_BUFFER, this.commandClientBufferBuilder.getBuffer());
}

GlVertexArray prevVao = null;

int commandStart = 0;
long commandStart = this.commandBuffer == null ? this.commandClientBufferBuilder.getBufferAddress() : 0L;

for (ChunkRegion region : this.pendingBatches) {
GlVertexArray vao = region.getVertexArray();
Expand All @@ -188,9 +194,9 @@ public void render(ChunkRenderListIterator renders, ChunkCameraContext camera) {
ChunkDrawCallBatcher batch = region.getDrawBatcher();

GlFunctions.INDIRECT_DRAW.glMultiDrawArraysIndirect(GL11.GL_QUADS, commandStart, batch.getCount(), 0 /* tightly packed */);
commandStart += batch.getArrayLength();

prevVao = vao;
commandStart += batch.getArrayLength();
}

this.pendingBatches.clear();
Expand All @@ -200,23 +206,23 @@ public void render(ChunkRenderListIterator renders, ChunkCameraContext camera) {
}

this.uniformBuffer.unbind(GL15.GL_ARRAY_BUFFER);
this.commandBuffer.unbind(GL40.GL_DRAW_INDIRECT_BUFFER);

if (this.commandBuffer != null) {
this.commandBuffer.unbind(GL40.GL_DRAW_INDIRECT_BUFFER);
}
}

private void setupCommandBuffers() {
this.commandBufferBuilder.begin();
private void buildCommandBuffer() {
this.commandClientBufferBuilder.begin();

for (ChunkRegion region : this.pendingBatches) {
ChunkDrawCallBatcher batcher = region.getDrawBatcher();
batcher.end();

this.commandBufferBuilder.pushCommandBuffer(batcher);
this.commandClientBufferBuilder.pushCommandBuffer(batcher);
}

this.commandBufferBuilder.end();

this.commandBuffer.bind(GL40.GL_DRAW_INDIRECT_BUFFER);
this.commandBuffer.upload(GL40.GL_DRAW_INDIRECT_BUFFER, this.commandBufferBuilder.getBuffer());
this.commandClientBufferBuilder.end();
}

private void setupArrayBufferState(GlBufferArena arena) {
Expand Down Expand Up @@ -335,23 +341,20 @@ public void delete() {

this.bufferManager.delete();
this.uploadBuffer.delete();
this.uniformBuffer.delete();
this.commandBuffer.delete();

if (this.commandBuffer != null) {
this.commandBuffer.delete();
}

this.commandClientBufferBuilder.delete();

this.uniformBuffer.delete();
this.uniformBufferBuilder.delete();
this.commandBufferBuilder.delete();
}

public static boolean isSupported(boolean disableBlacklist) {
if (!disableBlacklist) {
try {
if (isIntelGpu()) {
return false;
}
} catch (Exception e) {
SodiumClientMod.logger().warn("An unexpected exception was thrown while trying to parse " +
"the current OpenGL renderer's strings", e);
}
public static boolean isSupported(boolean disableDriverBlacklist) {
if (!disableDriverBlacklist && isKnownBrokenIntelDriver()) {
return false;
}

return GlFunctions.isVertexArraySupported() &&
Expand All @@ -362,19 +365,52 @@ public static boolean isSupported(boolean disableBlacklist) {

/**
* Determines whether or not the current OpenGL renderer is an integrated Intel GPU on Windows.
* These drivers on Windows are known to create significant trouble with the multi-draw renderer.
* These drivers on Windows are known to fail when using command buffers.
*/
private static boolean isIntelGpu() {
private static boolean isWindowsIntelDriver() {
// We only care about Windows
// The open-source drivers on Linux are not known to have driver bugs with multi-draw
// The open-source drivers on Linux are not known to have driver bugs with indirect command buffers
if (Util.getOperatingSystem() != Util.OperatingSystem.WINDOWS) {
return false;
}

String vendor = Objects.requireNonNull(GL11.glGetString(GL11.GL_VENDOR));

// Check to see if the GPU vendor is Intel
return vendor.equals("Intel");
return Objects.equals(GL11.glGetString(GL11.GL_VENDOR), "Intel");
}

/**
* Determines whether or not the current OpenGL renderer is an old integrated Intel GPU (prior to Skylake/Gen8) on
* Windows. These drivers on Windows are unsupported and known to create significant trouble with the multi-draw
* renderer.
*/
private static boolean isKnownBrokenIntelDriver() {
if (!isWindowsIntelDriver()) {
return false;
}

String renderer = Objects.requireNonNull(GL11.glGetString(GL11.GL_RENDERER));
String version = Objects.requireNonNull(GL11.glGetString(GL11.GL_VERSION));

// Check to see if the GPU's name matches any known Intel GPU names
if (!renderer.matches("^Intel\\(R\\) (U?HD|Iris( Pro)?) Graphics (\\d+)?$")) {
return false;
}

// https://www.intel.com/content/www/us/en/support/articles/000005654/graphics.html
Matcher matcher = Pattern.compile("(\\d.\\d.\\d) - Build (\\d+).(\\d+).(\\d+).(\\d+)")
.matcher(version);

// If the version pattern doesn't match, assume we're dealing with something special
if (!matcher.matches()) {
return false;
}

// The fourth group is the major build number
String majorBuildString = matcher.group(4);
int majorBuildNumber = Integer.parseInt(majorBuildString);

// Anything with a major build of >=100 is Gen8 or newer
return majorBuildNumber < 100;
}

@Override
Expand All @@ -391,8 +427,11 @@ public List<String> getDebugStrings() {
int ratio = (int) Math.floor(((double) used / (double) allocated) * 100.0D);

List<String> list = new ArrayList<>();
list.add(String.format("VRAM Pool: %d/%d MB (%d%%)", MemoryTracker.toMiB(used), MemoryTracker.toMiB(allocated), ratio));
list.add(String.format("Allocated Buffers: %s", this.bufferManager.getAllocatedRegionCount()));
list.add(String.format("VRAM Pool: %d/%d MiB %s(%d%%)%s", MemoryTracker.toMiB(used), MemoryTracker.toMiB(allocated),
ratio >= 95 ? Formatting.RED : Formatting.RESET, ratio, Formatting.RESET));
list.add(String.format("VRAM Regions: %s", this.bufferManager.getAllocatedRegionCount()));
list.add(String.format("Submission Mode: %s", this.commandBuffer != null ?
Formatting.AQUA + "Buffer" : Formatting.LIGHT_PURPLE + "Client Memory"));

return list;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,15 @@ public void begin() {
this.count = 0;
this.arrayLength = 0;

this.buffer.clear();
this.buffer.limit(this.buffer.capacity());
}

public void end() {
this.isBuilding = false;

this.arrayLength = this.count * STRIDE;
this.buffer.limit(this.arrayLength);
this.buffer.position(0);
}

public boolean isBuilding() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,8 @@ public ByteBuffer getBuffer() {
public void delete() {
MemoryUtil.memFree(this.buffer);
}

public long getBufferAddress() {
return MemoryUtil.memAddress(this.buffer);
}
}

1 comment on commit 34eb1d2

@Snivine
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ngl this is very pog, good job jelly

Please sign in to comment.